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 abfba3ad5..e19ca1eb1 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 + /// Language to convert. + /// Converted language. + public static Lumina.Data.Language ToLumina(this ClientLanguage language) { - /// - /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant. - /// - /// Language to convert. - /// Converted language. - 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 9ec6712aa..d4efde61c 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -10,368 +10,367 @@ 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 : IServiceType { - /// - /// Class containing Dalamud settings. - /// - [Serializable] - internal sealed class DalamudConfiguration : IServiceType + 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 key to opt into Dalamud staging builds. + /// + public string? DalamudBetaKey { get; set; } = null; + + /// + /// 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 to use AXIS fonts from the game. + /// + public bool UseAxisFontsFromGame { get; set; } = false; + + /// + /// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness. + /// + /// Before gamma is applied... + /// * ...TTF fonts loaded with stb or FreeType are in linear space. + /// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4. + /// + public float FontGammaLevel { get; set; } = 1.4f; + + /// + /// 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 to write to log files synchronously. + /// + public bool LogSynchronously { get; set; } = false; + + /// + /// 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 the dev bar should open at startup. + /// + public bool DevBarOpenAtStartup { 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 a value indicating whether to resume game main thread after plugins load. + /// + public bool IsResumeGameAfterPluginLoad { get; set; } = false; + + /// + /// Gets or sets the kind of beta to download when matches the server value. + /// + public string DalamudBetaKind { 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 value indicating the wait time between plugin unload and plugin assembly unload. + /// Uses default value that may change between versions if set to null. + /// + public int? PluginWaitBeforeFree { 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; } + + /// + /// Gets or sets the order of DTR elements, by title. + /// + public List? DtrOrder { get; set; } + + /// + /// Gets or sets the list of ignored DTR elements, by title. + /// + public List? DtrIgnore { get; set; } + + /// + /// Gets or sets the spacing used for DTR entries. + /// + public int DtrSpacing { get; set; } = 10; + + /// + /// Gets or sets a value indicating whether to swap the + /// direction in which elements are drawn in the DTR. + /// False indicates that elements will be drawn from the end of + /// the left side of the Server Info bar, and continue leftwards. + /// True indicates the opposite. + /// + public bool DtrSwapDirection { get; set; } = false; + + /// + /// Gets or sets a value indicating whether the title screen menu is shown. + /// + public bool ShowTsm { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not market board data should be uploaded. + /// + public bool IsMbCollect { get; set; } = true; + + /// + /// Gets the ISO 639-1 two-letter code for the language of the effective Dalamud display language. + /// + public string EffectiveLanguage + { + get { - 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 key to opt into Dalamud staging builds. - /// - public string? DalamudBetaKey { get; set; } = null; - - /// - /// 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 to use AXIS fonts from the game. - /// - public bool UseAxisFontsFromGame { get; set; } = false; - - /// - /// Gets or sets the gamma value to apply for Dalamud fonts. Effects text thickness. - /// - /// Before gamma is applied... - /// * ...TTF fonts loaded with stb or FreeType are in linear space. - /// * ...the game's prebaked AXIS fonts are in gamma space with gamma value of 1.4. - /// - public float FontGammaLevel { get; set; } = 1.4f; - - /// - /// 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 to write to log files synchronously. - /// - public bool LogSynchronously { get; set; } = false; - - /// - /// 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 the dev bar should open at startup. - /// - public bool DevBarOpenAtStartup { 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 a value indicating whether to resume game main thread after plugins load. - /// - public bool IsResumeGameAfterPluginLoad { get; set; } = false; - - /// - /// Gets or sets the kind of beta to download when matches the server value. - /// - public string DalamudBetaKind { 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 value indicating the wait time between plugin unload and plugin assembly unload. - /// Uses default value that may change between versions if set to null. - /// - public int? PluginWaitBeforeFree { 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; } - - /// - /// Gets or sets the order of DTR elements, by title. - /// - public List? DtrOrder { get; set; } - - /// - /// Gets or sets the list of ignored DTR elements, by title. - /// - public List? DtrIgnore { get; set; } - - /// - /// Gets or sets the spacing used for DTR entries. - /// - public int DtrSpacing { get; set; } = 10; - - /// - /// Gets or sets a value indicating whether to swap the - /// direction in which elements are drawn in the DTR. - /// False indicates that elements will be drawn from the end of - /// the left side of the Server Info bar, and continue leftwards. - /// True indicates the opposite. - /// - public bool DtrSwapDirection { get; set; } = false; - - /// - /// Gets or sets a value indicating whether the title screen menu is shown. - /// - public bool ShowTsm { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not market board data should be uploaded. - /// - public bool IsMbCollect { get; set; } = true; - - /// - /// Gets the ISO 639-1 two-letter code for the language of the effective Dalamud display language. - /// - public string EffectiveLanguage - { - get - { - var languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); - try - { - if (string.IsNullOrEmpty(this.LanguageOverride)) - { - var currentUiLang = CultureInfo.CurrentUICulture; - - if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x)) - return currentUiLang.TwoLetterISOLanguageName; - else - return languages[0]; - } - else - { - return this.LanguageOverride; - } - } - catch (Exception) - { - return languages[0]; - } - } - } - - /// - /// Gets or sets a value indicating whether or not to show info on dev bar. - /// - public bool ShowDevBarInfo { get; set; } = true; - - /// - /// Gets or sets the last-used contact details for the plugin feedback form. - /// - public string LastFeedbackContactDetails { get; set; } = string.Empty; - - /// - /// Gets or sets a list of plugins that testing builds should be downloaded for. - /// - public List? PluginTestingOptIns { 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 = null; + var languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); try { - deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings); + if (string.IsNullOrEmpty(this.LanguageOverride)) + { + var currentUiLang = CultureInfo.CurrentUICulture; + + if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x)) + return currentUiLang.TwoLetterISOLanguageName; + else + return languages[0]; + } + else + { + return this.LanguageOverride; + } } - catch (Exception ex) + catch (Exception) { - Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path); + return languages[0]; } - - deserialized ??= new DalamudConfiguration(); - 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); } } + + /// + /// Gets or sets a value indicating whether or not to show info on dev bar. + /// + public bool ShowDevBarInfo { get; set; } = true; + + /// + /// Gets or sets the last-used contact details for the plugin feedback form. + /// + public string LastFeedbackContactDetails { get; set; } = string.Empty; + + /// + /// Gets or sets a list of plugins that testing builds should be downloaded for. + /// + public List? PluginTestingOptIns { 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 = null; + 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; + } + + /// + /// 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..de083858d 100644 --- a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs +++ b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs @@ -1,24 +1,23 @@ -namespace Dalamud.Configuration +namespace Dalamud.Configuration; + +/// +/// 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 99a4c6709..a251da763 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 context menus should be disabled. - /// - public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU"); + /// + /// Gets a value indicating whether or not Dalamud context menus should be disabled. + /// + public static bool DalamudDoContextMenu { get; } = GetEnvironmentVariable("DALAMUD_ENABLE_CONTEXTMENU"); - 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..070fda408 100644 --- a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs +++ b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs @@ -1,29 +1,28 @@ -namespace Dalamud.Configuration +namespace Dalamud.Configuration; + +/// +/// 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 48522ea56..b917a9e79 100644 --- a/Dalamud/Configuration/PluginConfigurations.cs +++ b/Dalamud/Configuration/PluginConfigurations.cs @@ -2,149 +2,148 @@ 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, SerializeConfig(config)); + } + + /// + /// Load plugin configuration. + /// + /// Plugin name. + /// Plugin configuration. + public IPluginConfiguration? Load(string pluginName) + { + var path = this.GetConfigFile(pluginName); + + if (!path.Exists) + return null; + + return DeserializeConfig(File.ReadAllText(path.FullName)); + } + + /// + /// 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 { - this.configDirectory = new DirectoryInfo(storageFolder); - this.configDirectory.Create(); - } - - /// - /// 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, SerializeConfig(config)); - } - - /// - /// Load plugin configuration. - /// - /// Plugin name. - /// Plugin configuration. - public IPluginConfiguration? Load(string pluginName) - { - var path = this.GetConfigFile(pluginName); - + var path = this.GetDirectoryPath(pluginName); if (!path.Exists) - return null; - - return DeserializeConfig(File.ReadAllText(path.FullName)); - } - - /// - /// 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; + path.Create(); } + + return path.FullName; } - - /// - /// 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 + catch { - 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 + return string.Empty; } + } - /// - /// 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")); + /// + /// 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); - /// - /// Serializes a plugin configuration object. - /// - /// The configuration object. - /// A string representing the serialized configuration object. - internal static string SerializeConfig(object? config) + 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")); + + /// + /// Serializes a plugin configuration object. + /// + /// The configuration object. + /// A string representing the serialized configuration object. + internal static string SerializeConfig(object? config) + { + return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings { - return JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings + TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, + TypeNameHandling = TypeNameHandling.Objects, + }); + } + + /// + /// Deserializes a plugin configuration from a string. + /// + /// The serialized configuration. + /// The configuration object, or null. + internal static IPluginConfiguration? DeserializeConfig(string data) + { + return JsonConvert.DeserializeObject( + data, + new JsonSerializerSettings { TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, TypeNameHandling = TypeNameHandling.Objects, }); - } - - /// - /// Deserializes a plugin configuration from a string. - /// - /// The serialized configuration. - /// The configuration object, or null. - internal static IPluginConfiguration? DeserializeConfig(string data) - { - return JsonConvert.DeserializeObject( - data, - new JsonSerializerSettings - { - TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, - TypeNameHandling = TypeNameHandling.Objects, - }); - } - - private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName)); } + + private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName)); } diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index d78c36692..37375d87b 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -19,131 +19,130 @@ using Serilog; [assembly: InternalsVisibleTo("Dalamud.Test")] [assembly: InternalsVisibleTo("Dalamud.DevHelpers")] -namespace Dalamud +namespace Dalamud; + +/// +/// The main Dalamud class containing all subsystems. +/// +internal sealed class Dalamud : IServiceType { + #region Internals + + private readonly ManualResetEvent unloadSignal; + + #endregion + /// - /// The main Dalamud class containing all subsystems. + /// Initializes a new instance of the class. /// - internal sealed class Dalamud : IServiceType + /// DalamudStartInfo instance. + /// The Dalamud configuration. + /// Event used to signal the main thread to continue. + public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent) { - #region Internals + this.unloadSignal = new ManualResetEvent(false); + this.unloadSignal.Reset(); - private readonly ManualResetEvent unloadSignal; + ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration); - #endregion - - /// - /// Initializes a new instance of the class. - /// - /// DalamudStartInfo instance. - /// The Dalamud configuration. - /// Event used to signal the main thread to continue. - public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent) + if (!configuration.IsResumeGameAfterPluginLoad) { - this.unloadSignal = new ManualResetEvent(false); - this.unloadSignal.Reset(); - - ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration); - - if (!configuration.IsResumeGameAfterPluginLoad) + NativeFunctions.SetEvent(mainThreadContinueEvent); + try + { + _ = ServiceManager.InitializeEarlyLoadableServices(); + } + catch (Exception e) + { + Log.Error(e, "Service initialization failure"); + } + } + else + { + Task.Run(async () => { - NativeFunctions.SetEvent(mainThreadContinueEvent); try { - _ = ServiceManager.InitializeEarlyLoadableServices(); + var tasks = new[] + { + ServiceManager.InitializeEarlyLoadableServices(), + ServiceManager.BlockingResolved, + }; + + await Task.WhenAny(tasks); + var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray(); + if (faultedTasks.Any()) + throw new AggregateException(faultedTasks); + + NativeFunctions.SetEvent(mainThreadContinueEvent); + + await Task.WhenAll(tasks); } catch (Exception e) { Log.Error(e, "Service initialization failure"); } - } - else - { - Task.Run(async () => + finally { - try - { - var tasks = new[] - { - ServiceManager.InitializeEarlyLoadableServices(), - ServiceManager.BlockingResolved, - }; - - await Task.WhenAny(tasks); - var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray(); - if (faultedTasks.Any()) - throw new AggregateException(faultedTasks); - - NativeFunctions.SetEvent(mainThreadContinueEvent); - - await Task.WhenAll(tasks); - } - catch (Exception e) - { - Log.Error(e, "Service initialization failure"); - } - finally - { - NativeFunctions.SetEvent(mainThreadContinueEvent); - } - }); - } - } - - /// - /// Gets location of stored assets. - /// - internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory!); - - /// - /// 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(); - } - - /// - /// Dispose subsystems related to plugin handling. - /// - public void DisposePlugins() - { - // 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(); - } - - /// - /// 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); + NativeFunctions.SetEvent(mainThreadContinueEvent); + } + }); } } + + /// + /// Gets location of stored assets. + /// + internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory!); + + /// + /// 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(); + } + + /// + /// Dispose subsystems related to plugin handling. + /// + public void DisposePlugins() + { + // 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(); + } + + /// + /// 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); + } } diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs index 434624b3b..0010741bf 100644 --- a/Dalamud/DalamudStartInfo.cs +++ b/Dalamud/DalamudStartInfo.cs @@ -4,167 +4,166 @@ using System.Collections.Generic; using Dalamud.Game; using Newtonsoft.Json; -namespace Dalamud +namespace Dalamud; + +/// +/// Struct containing information needed to initialize Dalamud. +/// +[Serializable] +public record DalamudStartInfo : IServiceType { /// - /// Struct containing information needed to initialize Dalamud. + /// Initializes a new instance of the class. /// - [Serializable] - public record DalamudStartInfo : IServiceType + public DalamudStartInfo() { - /// - /// Initializes a new instance of the class. - /// - public DalamudStartInfo() - { - // ignored - } - - /// - /// Initializes a new instance of the class. - /// - /// Object to copy values from. - public DalamudStartInfo(DalamudStartInfo other) - { - this.WorkingDirectory = other.WorkingDirectory; - this.ConfigurationPath = other.ConfigurationPath; - this.PluginDirectory = other.PluginDirectory; - this.DefaultPluginDirectory = other.DefaultPluginDirectory; - this.AssetDirectory = other.AssetDirectory; - this.Language = other.Language; - this.GameVersion = other.GameVersion; - this.DelayInitializeMs = other.DelayInitializeMs; - this.TroubleshootingPackData = other.TroubleshootingPackData; - this.NoLoadPlugins = other.NoLoadPlugins; - this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins; - this.BootLogPath = other.BootLogPath; - this.BootShowConsole = other.BootShowConsole; - this.BootDisableFallbackConsole = other.BootDisableFallbackConsole; - this.BootWaitMessageBox = other.BootWaitMessageBox; - this.BootWaitDebugger = other.BootWaitDebugger; - this.BootVehEnabled = other.BootVehEnabled; - this.BootVehFull = other.BootVehFull; - this.BootEnableEtw = other.BootEnableEtw; - this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode; - this.BootEnabledGameFixes = other.BootEnabledGameFixes; - this.BootUnhookDlls = other.BootUnhookDlls; - this.CrashHandlerShow = other.CrashHandlerShow; - } - - /// - /// Gets or sets the working directory of the XIVLauncher installations. - /// - public string? WorkingDirectory { get; set; } - - /// - /// Gets or sets the path to the configuration file. - /// - public string? ConfigurationPath { get; set; } - - /// - /// Gets or sets the path to the directory for installed plugins. - /// - public string? PluginDirectory { get; set; } - - /// - /// Gets or sets the path to the directory for developer plugins. - /// - public string? DefaultPluginDirectory { get; set; } - - /// - /// Gets or sets the path to core Dalamud assets. - /// - public string? AssetDirectory { get; set; } - - /// - /// Gets or sets the language of the game client. - /// - public ClientLanguage Language { get; set; } = ClientLanguage.English; - - /// - /// Gets or sets the current game version code. - /// - [JsonConverter(typeof(GameVersionConverter))] - public GameVersion? GameVersion { get; set; } - - /// - /// Gets or sets troubleshooting information to attach when generating a tspack file. - /// - public string TroubleshootingPackData { get; set; } - - /// - /// Gets or sets a value that specifies how much to wait before a new Dalamud session. - /// - public int DelayInitializeMs { get; set; } - - /// - /// Gets or sets a value indicating whether no plugins should be loaded. - /// - public bool NoLoadPlugins { get; set; } - - /// - /// Gets or sets a value indicating whether no third-party plugins should be loaded. - /// - public bool NoLoadThirdPartyPlugins { get; set; } - - /// - /// Gets or sets the path the boot log file is supposed to be written to. - /// - public string? BootLogPath { get; set; } - - /// - /// Gets or sets a value indicating whether a Boot console should be shown. - /// - public bool BootShowConsole { get; set; } - - /// - /// Gets or sets a value indicating whether the fallback console should be shown, if needed. - /// - public bool BootDisableFallbackConsole { get; set; } - - /// - /// Gets or sets a flag indicating where Dalamud should wait with a message box. - /// - public int BootWaitMessageBox { get; set; } - - /// - /// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing. - /// - public bool BootWaitDebugger { get; set; } - - /// - /// Gets or sets a value indicating whether the VEH should be enabled. - /// - public bool BootVehEnabled { get; set; } - - /// - /// Gets or sets a value indicating whether the VEH should be doing full crash dumps. - /// - public bool BootVehFull { get; set; } - - /// - /// Gets or sets a value indicating whether or not ETW should be enabled. - /// - public bool BootEnableEtw { get; set; } - - /// - /// Gets or sets a value choosing the OpenProcess hookmode. - /// - public int BootDotnetOpenProcessHookMode { get; set; } - - /// - /// Gets or sets a list of enabled game fixes. - /// - public List? BootEnabledGameFixes { get; set; } - - /// - /// Gets or sets a list of DLLs that should be unhooked. - /// - public List? BootUnhookDlls { get; set; } - - /// - /// Gets or sets a value indicating whether to show crash handler console window. - /// - public bool CrashHandlerShow { get; set; } + // ignored } + + /// + /// Initializes a new instance of the class. + /// + /// Object to copy values from. + public DalamudStartInfo(DalamudStartInfo other) + { + this.WorkingDirectory = other.WorkingDirectory; + this.ConfigurationPath = other.ConfigurationPath; + this.PluginDirectory = other.PluginDirectory; + this.DefaultPluginDirectory = other.DefaultPluginDirectory; + this.AssetDirectory = other.AssetDirectory; + this.Language = other.Language; + this.GameVersion = other.GameVersion; + this.DelayInitializeMs = other.DelayInitializeMs; + this.TroubleshootingPackData = other.TroubleshootingPackData; + this.NoLoadPlugins = other.NoLoadPlugins; + this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins; + this.BootLogPath = other.BootLogPath; + this.BootShowConsole = other.BootShowConsole; + this.BootDisableFallbackConsole = other.BootDisableFallbackConsole; + this.BootWaitMessageBox = other.BootWaitMessageBox; + this.BootWaitDebugger = other.BootWaitDebugger; + this.BootVehEnabled = other.BootVehEnabled; + this.BootVehFull = other.BootVehFull; + this.BootEnableEtw = other.BootEnableEtw; + this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode; + this.BootEnabledGameFixes = other.BootEnabledGameFixes; + this.BootUnhookDlls = other.BootUnhookDlls; + this.CrashHandlerShow = other.CrashHandlerShow; + } + + /// + /// Gets or sets the working directory of the XIVLauncher installations. + /// + public string? WorkingDirectory { get; set; } + + /// + /// Gets or sets the path to the configuration file. + /// + public string? ConfigurationPath { get; set; } + + /// + /// Gets or sets the path to the directory for installed plugins. + /// + public string? PluginDirectory { get; set; } + + /// + /// Gets or sets the path to the directory for developer plugins. + /// + public string? DefaultPluginDirectory { get; set; } + + /// + /// Gets or sets the path to core Dalamud assets. + /// + public string? AssetDirectory { get; set; } + + /// + /// Gets or sets the language of the game client. + /// + public ClientLanguage Language { get; set; } = ClientLanguage.English; + + /// + /// Gets or sets the current game version code. + /// + [JsonConverter(typeof(GameVersionConverter))] + public GameVersion? GameVersion { get; set; } + + /// + /// Gets or sets troubleshooting information to attach when generating a tspack file. + /// + public string TroubleshootingPackData { get; set; } + + /// + /// Gets or sets a value that specifies how much to wait before a new Dalamud session. + /// + public int DelayInitializeMs { get; set; } + + /// + /// Gets or sets a value indicating whether no plugins should be loaded. + /// + public bool NoLoadPlugins { get; set; } + + /// + /// Gets or sets a value indicating whether no third-party plugins should be loaded. + /// + public bool NoLoadThirdPartyPlugins { get; set; } + + /// + /// Gets or sets the path the boot log file is supposed to be written to. + /// + public string? BootLogPath { get; set; } + + /// + /// Gets or sets a value indicating whether a Boot console should be shown. + /// + public bool BootShowConsole { get; set; } + + /// + /// Gets or sets a value indicating whether the fallback console should be shown, if needed. + /// + public bool BootDisableFallbackConsole { get; set; } + + /// + /// Gets or sets a flag indicating where Dalamud should wait with a message box. + /// + public int BootWaitMessageBox { get; set; } + + /// + /// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing. + /// + public bool BootWaitDebugger { get; set; } + + /// + /// Gets or sets a value indicating whether the VEH should be enabled. + /// + public bool BootVehEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether the VEH should be doing full crash dumps. + /// + public bool BootVehFull { get; set; } + + /// + /// Gets or sets a value indicating whether or not ETW should be enabled. + /// + public bool BootEnableEtw { get; set; } + + /// + /// Gets or sets a value choosing the OpenProcess hookmode. + /// + public int BootDotnetOpenProcessHookMode { get; set; } + + /// + /// Gets or sets a list of enabled game fixes. + /// + public List? BootEnabledGameFixes { get; set; } + + /// + /// Gets or sets a list of DLLs that should be unhooked. + /// + public List? BootUnhookDlls { get; set; } + + /// + /// Gets or sets a value indicating whether to show crash handler console window. + /// + public bool CrashHandlerShow { get; set; } } diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 48cd0d325..8cdb58dcd 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -19,331 +19,330 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class DataManager : IDisposable, IServiceType { - /// - /// This class provides data for Dalamud-internal features, but can also be used by plugins if needed. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class DataManager : IDisposable, IServiceType + private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; + + private readonly Thread luminaResourceThread; + private readonly CancellationTokenSource luminaCancellationTokenSource; + + [ServiceManager.ServiceConstructor] + private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud) { - private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; + this.Language = dalamudStartInfo.Language; - private readonly Thread luminaResourceThread; - private readonly 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()); - [ServiceManager.ServiceConstructor] - private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud) + var baseDir = dalamud.AssetDirectory.FullName; + try { - this.Language = dalamudStartInfo.Language; + Log.Verbose("Starting data load..."); - // Set up default values so plugins do not null-reference when data is being loaded. - this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); + var zoneOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!; + this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); - var baseDir = dalamud.AssetDirectory.FullName; - try + 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); + + using (Timings.Start("Lumina Init")) { - 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); - - using (Timings.Start("Lumina Init")) + var luminaOptions = new LuminaOptions { - var luminaOptions = new LuminaOptions - { - LoadMultithreaded = true, - CacheFileResources = true, + LoadMultithreaded = true, + CacheFileResources = true, #if DEBUG PanicOnSheetChecksumMismatch = true, #else - PanicOnSheetChecksumMismatch = false, + PanicOnSheetChecksumMismatch = false, #endif - DefaultExcelLanguage = this.Language.ToLumina(), - }; + DefaultExcelLanguage = this.Language.ToLumina(), + }; - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions); + } + else + { + throw new Exception("Could not main module."); + } + + 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 = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions); + this.GameData.ProcessFileHandleQueue(); } else { - throw new Exception("Could not main module."); + Thread.Sleep(5); } - - 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."); - } + }); + this.luminaResourceThread.Start(); } - - /// - /// 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 + catch (Exception ex) { - 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); - 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); - - /// - /// 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. - /// - void IDisposable.Dispose() - { - this.luminaCancellationTokenSource.Cancel(); + Log.Error(ex, "Could not download data."); } } + + /// + /// 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 + { + 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); + 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); + + /// + /// 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. + /// + void IDisposable.Dispose() + { + this.luminaCancellationTokenSource.Cancel(); + } } diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index a4275afcd..219b71a64 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -18,341 +18,340 @@ 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. + /// Log level switch for runtime log level change. /// - public sealed class EntryPoint + public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose); + + /// + /// A delegate used during initialization of the CLR from Dalamud.Boot. + /// + /// Pointer to a serialized data. + /// Event used to signal the main thread to continue. + public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent); + + /// + /// A delegate used from VEH handler on exception which CoreCLR will fast fail by default. + /// + /// HGLOBAL for message. + public delegate IntPtr VehDelegate(); + + /// + /// Initialize Dalamud. + /// + /// Pointer to a serialized data. + /// Event used to signal the main thread to continue. + public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent) { - /// - /// Log level switch for runtime log level change. - /// - public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose); + var infoStr = Marshal.PtrToStringUTF8(infoPtr)!; + var info = JsonConvert.DeserializeObject(infoStr)!; - /// - /// A delegate used during initialization of the CLR from Dalamud.Boot. - /// - /// Pointer to a serialized data. - /// Event used to signal the main thread to continue. - public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent); + if ((info.BootWaitMessageBox & 4) != 0) + MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok); - /// - /// A delegate used from VEH handler on exception which CoreCLR will fast fail by default. - /// - /// HGLOBAL for message. - public delegate IntPtr VehDelegate(); + new Thread(() => RunThread(info, mainThreadContinueEvent)).Start(); + } - /// - /// Initialize Dalamud. - /// - /// Pointer to a serialized data. - /// Event used to signal the main thread to continue. - public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent) + /// + /// Returns stack trace. + /// + /// HGlobal to wchar_t* stack trace c-string. + public static IntPtr VehCallback() + { + try { - var infoStr = Marshal.PtrToStringUTF8(infoPtr)!; - var info = JsonConvert.DeserializeObject(infoStr)!; - - if ((info.BootWaitMessageBox & 4) != 0) - MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok); - - new Thread(() => RunThread(info, mainThreadContinueEvent)).Start(); + return Marshal.StringToHGlobalUni(Environment.StackTrace); } - - /// - /// Returns stack trace. - /// - /// HGlobal to wchar_t* stack trace c-string. - public static IntPtr VehCallback() + catch (Exception e) { - try - { - return Marshal.StringToHGlobalUni(Environment.StackTrace); - } - catch (Exception e) - { - return Marshal.StringToHGlobalUni("Fail: " + e); - } + return Marshal.StringToHGlobalUni("Fail: " + e); } + } - /// - /// Sets up logging. - /// - /// Base directory. - /// Whether to log to console. - /// Log synchronously. - internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously) - { + /// + /// Sets up logging. + /// + /// Base directory. + /// Whether to log to console. + /// Log synchronously. + internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously) + { #if DEBUG var logPath = Path.Combine(baseDirectory, "dalamud.log"); var oldPath = Path.Combine(baseDirectory, "dalamud.old.log"); var oldPathOld = Path.Combine(baseDirectory, "dalamud.log.old"); #else - var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log"); - var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.old.log"); - var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old"); + var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log"); + var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.old.log"); + var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old"); #endif - Log.CloseAndFlush(); + Log.CloseAndFlush(); - var oldFileOld = new FileInfo(oldPathOld); - if (oldFileOld.Exists) - { - var oldFile = new FileInfo(oldPath); - if (oldFile.Exists) - oldFileOld.Delete(); - else - oldFileOld.MoveTo(oldPath); - } - - CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024); - - var config = new LoggerConfiguration() - .WriteTo.Sink(SerilogEventSink.Instance) - .MinimumLevel.ControlledBy(LogLevelSwitch); - - if (logSynchronously) - { - config = config.WriteTo.File(logPath, fileSizeLimitBytes: null); - } - else - { - config = config.WriteTo.Async(a => a.File( - logPath, - fileSizeLimitBytes: null, - buffered: false, - flushToDiskInterval: TimeSpan.FromSeconds(1))); - } - - if (logConsole) - config = config.WriteTo.Console(); - - Log.Logger = config.CreateLogger(); - } - - /// - /// Initialize all Dalamud subsystems and start running on the main thread. - /// - /// The containing information needed to initialize Dalamud. - /// Event used to signal the main thread to continue. - private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent) + var oldFileOld = new FileInfo(oldPathOld); + if (oldFileOld.Exists) { - // Setup logger - InitLogging(info.WorkingDirectory!, info.BootShowConsole, true); - SerilogEventSink.Instance.LogLine += SerilogOnLogLine; - - // 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 -#if !DEBUG - if (!configuration.LogSynchronously) - InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously); - LogLevelSwitch.MinimumLevel = configuration.LogLevel; -#endif - - // Log any unhandled exception. - AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; - TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; - - 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, configuration, mainThreadContinueEvent); - Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs()); - - dalamud.WaitForUnload(); - - ServiceManager.UnloadAllServices(); - } - 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(); - SerilogEventSink.Instance.LogLine -= SerilogOnLogLine; - } - } - - private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev) - { - if (ev.LogEvent.Exception == null) - return; - - // Don't pass verbose/debug/info exceptions to the troubleshooter, as the developer is probably doing - // something intentionally (or this is known). - if (ev.LogEvent.Level < LogEventLevel.Warning) - return; - - Troubleshooting.LogException(ev.LogEvent.Exception, ev.Line); - } - - 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."); - } - } - - /// - /// Trim existing log file to a specified length, and optionally move the excess data to another file. - /// - /// Target log file to trim. - /// Maximum size of target log file. - /// .old file to move excess data to. - /// Maximum size of .old file. - private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize) - { - var logFile = new FileInfo(logPath); var oldFile = new FileInfo(oldPath); - var targetFiles = new[] - { - (logFile, logMaxSize), - (oldFile, oldMaxSize), - }; - var buffer = new byte[4096]; - - try - { - if (!logFile.Exists) - logFile.Create().Close(); - - // 1. Move excess data from logFile to oldFile - if (logFile.Length > logMaxSize) - { - using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite); - - var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize); - reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End); - - for (var i = 0; i < amountToMove; i += buffer.Length) - writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i))); - } - - // 2. Cull each of .log and .old files - foreach (var (file, maxSize) in targetFiles) - { - if (!file.Exists || file.Length <= maxSize) - continue; - - using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite); - - reader.Seek(file.Length - maxSize, SeekOrigin.Begin); - for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;) - writer.Write(buffer, 0, read); - - writer.SetLength(maxSize); - } - } - catch (Exception ex) - { - if (ex is IOException) - { - foreach (var (file, _) in targetFiles) - { - try - { - if (file.Exists) - file.Delete(); - } - catch (Exception ex2) - { - Log.Error(ex2, "Failed to delete {file}", file.FullName); - } - } - } - - 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); - */ - } + if (oldFile.Exists) + oldFileOld.Delete(); + else + oldFileOld.MoveTo(oldPath); } - private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) + CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024); + + var config = new LoggerConfiguration() + .WriteTo.Sink(SerilogEventSink.Instance) + .MinimumLevel.ControlledBy(LogLevelSwitch); + + if (logSynchronously) { - 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(); - } - - Log.CloseAndFlush(); - Environment.Exit(-1); - break; - default: - Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); - - Log.CloseAndFlush(); - Environment.Exit(-1); - break; - } + config = config.WriteTo.File(logPath, fileSizeLimitBytes: null); + } + else + { + config = config.WriteTo.Async(a => a.File( + logPath, + fileSizeLimitBytes: null, + buffered: false, + flushToDiskInterval: TimeSpan.FromSeconds(1))); } - private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) + if (logConsole) + config = config.WriteTo.Console(); + + Log.Logger = config.CreateLogger(); + } + + /// + /// Initialize all Dalamud subsystems and start running on the main thread. + /// + /// The containing information needed to initialize Dalamud. + /// Event used to signal the main thread to continue. + private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent) + { + // Setup logger + InitLogging(info.WorkingDirectory!, info.BootShowConsole, true); + SerilogEventSink.Instance.LogLine += SerilogOnLogLine; + + // 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 +#if !DEBUG + if (!configuration.LogSynchronously) + InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously); + LogLevelSwitch.MinimumLevel = configuration.LogLevel; +#endif + + // Log any unhandled exception. + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + + try { - if (!args.Observed) - Log.Error(args.Exception, "Unobserved exception in Task."); + 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, configuration, mainThreadContinueEvent); + Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs()); + + dalamud.WaitForUnload(); + + ServiceManager.UnloadAllServices(); + } + 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(); + SerilogEventSink.Instance.LogLine -= SerilogOnLogLine; } } + + private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev) + { + if (ev.LogEvent.Exception == null) + return; + + // Don't pass verbose/debug/info exceptions to the troubleshooter, as the developer is probably doing + // something intentionally (or this is known). + if (ev.LogEvent.Level < LogEventLevel.Warning) + return; + + Troubleshooting.LogException(ev.LogEvent.Exception, ev.Line); + } + + 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."); + } + } + + /// + /// Trim existing log file to a specified length, and optionally move the excess data to another file. + /// + /// Target log file to trim. + /// Maximum size of target log file. + /// .old file to move excess data to. + /// Maximum size of .old file. + private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize) + { + var logFile = new FileInfo(logPath); + var oldFile = new FileInfo(oldPath); + var targetFiles = new[] + { + (logFile, logMaxSize), + (oldFile, oldMaxSize), + }; + var buffer = new byte[4096]; + + try + { + if (!logFile.Exists) + logFile.Create().Close(); + + // 1. Move excess data from logFile to oldFile + if (logFile.Length > logMaxSize) + { + using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + + var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize); + reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End); + + for (var i = 0; i < amountToMove; i += buffer.Length) + writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i))); + } + + // 2. Cull each of .log and .old files + foreach (var (file, maxSize) in targetFiles) + { + if (!file.Exists || file.Length <= maxSize) + continue; + + using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite); + + reader.Seek(file.Length - maxSize, SeekOrigin.Begin); + for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;) + writer.Write(buffer, 0, read); + + writer.SetLength(maxSize); + } + } + catch (Exception ex) + { + if (ex is IOException) + { + foreach (var (file, _) in targetFiles) + { + try + { + if (file.Exists) + file.Delete(); + } + catch (Exception ex2) + { + Log.Error(ex2, "Failed to delete {file}", file.FullName); + } + } + } + + 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); + */ + } + } + + 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(); + } + + Log.CloseAndFlush(); + Environment.Exit(-1); + break; + default: + Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); + + Log.CloseAndFlush(); + Environment.Exit(-1); + 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 81449bb07..24e7dffe8 100644 --- a/Dalamud/Game/BaseAddressResolver.cs +++ b/Dalamud/Game/BaseAddressResolver.cs @@ -5,114 +5,113 @@ using System.Runtime.InteropServices; using JetBrains.Annotations; -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 appropriate method based on the process architecture, + /// using the default SigScanner. + /// + /// For plugins. Not intended to be called from Dalamud Service{T} constructors. + /// + [UsedImplicitly] + public void Setup() => this.Setup(Service.Get()); + + /// + /// Setup the resolver, calling the appropriate method based on the process architecture. + /// + /// The SigScanner instance. + public void Setup(SigScanner scanner) { - /// - /// Gets a list of memory addresses that were found, to list in /xldata. - /// - public static Dictionary> DebugScannedValues { get; } = new(); + // Because C# don't allow to call virtual function while in ctor + // we have to do this shit :\ - /// - /// Gets or sets a value indicating whether the resolver has successfully run or . - /// - protected bool IsResolved { get; set; } - - /// - /// Setup the resolver, calling the appropriate method based on the process architecture, - /// using the default SigScanner. - /// - /// For plugins. Not intended to be called from Dalamud Service{T} constructors. - /// - [UsedImplicitly] - public void Setup() => this.Setup(Service.Get()); - - /// - /// Setup the resolver, calling the appropriate method based on the process architecture. - /// - /// The SigScanner instance. - public void Setup(SigScanner scanner) + if (this.IsResolved) { - // 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; - var list = new List<(string, IntPtr)>(); - lock (DebugScannedValues) - DebugScannedValues[className] = list; - - foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) - { - list.Add((property.Name, (IntPtr)property.GetValue(this))); - } - - this.IsResolved = true; + return; } - /// - /// 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 + if (scanner.Is32BitProcess) { - // 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); + this.Setup32Bit(scanner); + } + else + { + this.Setup64Bit(scanner); } - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void Setup32Bit(SigScanner scanner) + this.SetupInternal(scanner); + + var className = this.GetType().Name; + var list = new List<(string, IntPtr)>(); + lock (DebugScannedValues) + DebugScannedValues[className] = list; + + foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) { - throw new NotSupportedException("32 bit version is not supported."); + list.Add((property.Name, (IntPtr)property.GetValue(this))); } - /// - /// 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."); - } + this.IsResolved = true; + } - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void SetupInternal(SigScanner scanner) - { - // Do nothing - } + /// + /// 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); + + // 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 3e489af82..03e28073a 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -20,314 +20,313 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public class ChatHandlers : IServiceType { - /// - /// Chat events and public helper functions. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public class ChatHandlers : IServiceType + // 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|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|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%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + 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|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|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%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - 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[] - { - new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), - } - }, - { - 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; - - [ServiceManager.ServiceDependency] - private readonly DalamudConfiguration configuration = Service.Get(); - - private bool hasSeenLoadingMsg; - private bool hasAutoUpdatedPlugins; - - [ServiceManager.ServiceConstructor] - private ChatHandlers(ChatGui chatGui) - { - chatGui.CheckMessageHandled += this.OnCheckMessageHandled; - chatGui.ChatMessage += this.OnChatMessage; - - this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) => - { - Service.GetNullable()?.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 textVal = message.TextValue; - - if (!this.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; - } + new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), + new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), } - - if (this.configuration.BadWords != null && - this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) + }, + { + ClientLanguage.English, + new Regex[] { - // This seems to be in the user block list - let's not show it - Log.Debug("Blocklist triggered"); + new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), + } + }, + { + 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; + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration configuration = Service.Get(); + + private bool hasSeenLoadingMsg; + private bool hasAutoUpdatedPlugins; + + [ServiceManager.ServiceConstructor] + private ChatHandlers(ChatGui chatGui) + { + chatGui.CheckMessageHandled += this.OnCheckMessageHandled; + chatGui.ChatMessage += this.OnChatMessage; + + this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) => + { + Service.GetNullable()?.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 textVal = message.TextValue; + + if (!this.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 (this.configuration.BadWords != null && + this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) { - var startInfo = Service.Get(); - var clientState = Service.GetNullable(); - if (clientState == null) - return; + // 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.GetNullable(); + if (clientState == null) + return; - // 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.GetNullable(); + var pluginManager = Service.GetNullable(); + var dalamudInterface = Service.GetNullable(); + + if (chatGui == null || pluginManager == null || dalamudInterface == null) + return; + + 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(x => x.IsLoaded))); + + if (this.configuration.PrintPluginsWelcomeMsg) { - var chatGui = Service.GetNullable(); - var pluginManager = Service.GetNullable(); - var dalamudInterface = Service.GetNullable(); - - if (chatGui == null || pluginManager == null || dalamudInterface == null) - return; - - 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(x => x.IsLoaded))); - - if (this.configuration.PrintPluginsWelcomeMsg) + foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded)) { - foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded)) - { - 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(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion)) - { - chatGui.PrintChat(new XivChatEntry - { - Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."), - Type = XivChatType.Notice, - }); - - if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor))) - { - dalamudInterface.OpenChangelogWindow(); - this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor; - } - - this.configuration.LastVersion = assemblyVersion; - this.configuration.Save(); - } - - this.hasSeenLoadingMsg = true; } - private void AutoUpdatePlugins() + if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion)) { - var chatGui = Service.GetNullable(); - var pluginManager = Service.GetNullable(); - var notifications = Service.GetNullable(); - - if (chatGui == null || pluginManager == null || notifications == null) - return; - - if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0) + chatGui.PrintChat(new XivChatEntry { - // Plugins aren't ready yet. - return; - } - - this.hasAutoUpdatedPlugins = true; - - Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task => - { - if (task.IsFaulted) - { - Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates.")); - return; - } - - var updatedPlugins = task.Result; - if (updatedPlugins.Any()) - { - if (this.configuration.AutoUpdatePlugins) - { - Service.Get().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 - { - 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), - this.openInstallerWindowLink, - new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")), - RawPayload.LinkTerminator, - new UIForegroundPayload(0), - new TextPayload("]"), - }), - Type = XivChatType.Urgent, - }); - } - } + Message = Loc.Localize("DalamudUpdated", "Dalamud has been updated successfully! Please check the discord for a full changelog."), + Type = XivChatType.Notice, }); + + if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor))) + { + dalamudInterface.OpenChangelogWindow(); + this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor; + } + + this.configuration.LastVersion = assemblyVersion; + this.configuration.Save(); } + + this.hasSeenLoadingMsg = true; + } + + private void AutoUpdatePlugins() + { + var chatGui = Service.GetNullable(); + var pluginManager = Service.GetNullable(); + var notifications = Service.GetNullable(); + + if (chatGui == null || pluginManager == null || notifications == null) + return; + + if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0) + { + // Plugins aren't ready yet. + return; + } + + this.hasAutoUpdatedPlugins = true; + + Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task => + { + if (task.IsFaulted) + { + Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates.")); + return; + } + + var updatedPlugins = task.Result; + if (updatedPlugins.Any()) + { + if (this.configuration.AutoUpdatePlugins) + { + Service.Get().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 + { + 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), + this.openInstallerWindowLink, + new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")), + RawPayload.LinkTerminator, + new UIForegroundPayload(0), + new TextPayload("]"), + }), + Type = XivChatType.Urgent, + }); + } + } + }); } } diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs index 9ada955f2..8113e0593 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs @@ -1,72 +1,71 @@ using Dalamud.Game.ClientState.Resolvers; using FFXIVClientStructs.FFXIV.Client.Game.UI; -namespace Dalamud.Game.ClientState.Aetherytes +namespace Dalamud.Game.ClientState.Aetherytes; + +/// +/// This class represents an entry in the Aetheryte list. +/// +public sealed class AetheryteEntry { + private readonly TeleportInfo data; + /// - /// This class represents an entry in the Aetheryte list. + /// Initializes a new instance of the class. /// - public sealed class AetheryteEntry + /// Data read from the Aetheryte List. + internal AetheryteEntry(TeleportInfo data) { - private readonly TeleportInfo data; - - /// - /// Initializes a new instance of the class. - /// - /// Data read from the Aetheryte List. - internal AetheryteEntry(TeleportInfo data) - { - this.data = data; - } - - /// - /// Gets the Aetheryte ID. - /// - public uint AetheryteId => this.data.AetheryteId; - - /// - /// Gets the Territory ID. - /// - public uint TerritoryId => this.data.TerritoryId; - - /// - /// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.). - /// - public byte SubIndex => this.data.SubIndex; - - /// - /// Gets the Ward. Zero if not a Shared Estate. - /// - public byte Ward => this.data.Ward; - - /// - /// Gets the Plot. Zero if not a Shared Estate. - /// - public byte Plot => this.data.Plot; - - /// - /// Gets the Cost in Gil to Teleport to this location. - /// - public uint GilCost => this.data.GilCost; - - /// - /// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not. - /// - public bool IsFavourite => this.data.IsFavourite != 0; - - /// - /// Gets a value indicating whether this Aetheryte is a Shared Estate or not. - /// - public bool IsSharedHouse => this.data.IsSharedHouse; - - /// - /// Gets a value indicating whether this Aetheryte is an Appartment or not. - /// - public bool IsAppartment => this.data.IsAppartment; - - /// - /// Gets the Aetheryte data related to this aetheryte. - /// - public ExcelResolver AetheryteData => new(this.AetheryteId); + this.data = data; } + + /// + /// Gets the Aetheryte ID. + /// + public uint AetheryteId => this.data.AetheryteId; + + /// + /// Gets the Territory ID. + /// + public uint TerritoryId => this.data.TerritoryId; + + /// + /// Gets the SubIndex used when there can be multiple Aetherytes with the same ID (Private/Shared Estates etc.). + /// + public byte SubIndex => this.data.SubIndex; + + /// + /// Gets the Ward. Zero if not a Shared Estate. + /// + public byte Ward => this.data.Ward; + + /// + /// Gets the Plot. Zero if not a Shared Estate. + /// + public byte Plot => this.data.Plot; + + /// + /// Gets the Cost in Gil to Teleport to this location. + /// + public uint GilCost => this.data.GilCost; + + /// + /// Gets a value indicating whether the LocalPlayer has set this Aetheryte as Favorite or not. + /// + public bool IsFavourite => this.data.IsFavourite != 0; + + /// + /// Gets a value indicating whether this Aetheryte is a Shared Estate or not. + /// + public bool IsSharedHouse => this.data.IsSharedHouse; + + /// + /// Gets a value indicating whether this Aetheryte is an Appartment or not. + /// + public bool IsAppartment => this.data.IsAppartment; + + /// + /// Gets the Aetheryte data related to this aetheryte. + /// + public ExcelResolver AetheryteData => new(this.AetheryteId); } diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs index dd735bd42..46b285c68 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -7,105 +7,104 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Aetherytes +namespace Dalamud.Game.ClientState.Aetherytes; + +/// +/// This collection represents the list of available Aetherytes in the Teleport window. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed partial class AetheryteList : IServiceType { - /// - /// This collection represents the list of available Aetherytes in the Teleport window. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class AetheryteList : IServiceType + [ServiceManager.ServiceDependency] + private readonly ClientState clientState = Service.Get(); + private readonly ClientStateAddressResolver address; + private readonly UpdateAetheryteListDelegate updateAetheryteListFunc; + + [ServiceManager.ServiceConstructor] + private AetheryteList() { - [ServiceManager.ServiceDependency] - private readonly ClientState clientState = Service.Get(); - private readonly ClientStateAddressResolver address; - private readonly UpdateAetheryteListDelegate updateAetheryteListFunc; + this.address = this.clientState.AddressResolver; + this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList); - [ServiceManager.ServiceConstructor] - private AetheryteList() + Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}"); + } + + private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1); + + /// + /// Gets the amount of Aetherytes the local player has unlocked. + /// + public unsafe int Length + { + get { - this.address = this.clientState.AddressResolver; - this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList); - - Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}"); - } - - private delegate void UpdateAetheryteListDelegate(IntPtr telepo, byte arg1); - - /// - /// Gets the amount of Aetherytes the local player has unlocked. - /// - public unsafe int Length - { - get - { - if (this.clientState.LocalPlayer == null) - return 0; - - this.Update(); - - if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last) - return 0; - - return (int)TelepoStruct->TeleportList.Size(); - } - } - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo; - - /// - /// Gets a Aetheryte Entry at the specified index. - /// - /// Index. - /// A at the specified index. - public unsafe AetheryteEntry? this[int index] - { - get - { - if (index < 0 || index >= this.Length) - { - return null; - } - - if (this.clientState.LocalPlayer == null) - return null; - - return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index)); - } - } - - private void Update() - { - // this is very very important as otherwise it crashes if (this.clientState.LocalPlayer == null) - return; + return 0; - this.updateAetheryteListFunc(this.address.Telepo, 0); + this.Update(); + + if (TelepoStruct->TeleportList.First == TelepoStruct->TeleportList.Last) + return 0; + + return (int)TelepoStruct->TeleportList.Size(); } } + private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo* TelepoStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Telepo*)this.address.Telepo; + /// - /// This collection represents the list of available Aetherytes in the Teleport window. + /// Gets a Aetheryte Entry at the specified index. /// - public sealed partial class AetheryteList : IReadOnlyCollection + /// Index. + /// A at the specified index. + public unsafe AetheryteEntry? this[int index] { - /// - public int Count => this.Length; - - /// - public IEnumerator GetEnumerator() + get { - for (var i = 0; i < this.Length; i++) + if (index < 0 || index >= this.Length) { - yield return this[i]; + return null; } - } - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); + if (this.clientState.LocalPlayer == null) + return null; + + return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index)); } } + + private void Update() + { + // this is very very important as otherwise it crashes + if (this.clientState.LocalPlayer == null) + return; + + this.updateAetheryteListFunc(this.address.Telepo, 0); + } +} + +/// +/// This collection represents the list of available Aetherytes in the Teleport window. +/// +public sealed partial class AetheryteList : IReadOnlyCollection +{ + /// + public int Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < this.Length; i++) + { + yield return this[i]; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index a345d27e2..0566a1f76 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -7,179 +7,178 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed partial class BuddyList : IServiceType { - /// - /// This collection represents the buddies present in your squadron or trust party. - /// It does not include the local player. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class BuddyList : IServiceType + private const uint InvalidObjectID = 0xE0000000; + + [ServiceManager.ServiceDependency] + private readonly ClientState clientState = Service.Get(); + + private readonly ClientStateAddressResolver address; + + [ServiceManager.ServiceConstructor] + private BuddyList() { - private const uint InvalidObjectID = 0xE0000000; + this.address = this.clientState.AddressResolver; - [ServiceManager.ServiceDependency] - private readonly ClientState clientState = Service.Get(); + Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); + } - private readonly ClientStateAddressResolver address; - - [ServiceManager.ServiceConstructor] - private BuddyList() + /// + /// Gets the amount of battle buddies the local player has. + /// + public int Length + { + get { - this.address = this.clientState.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) - { - if (this.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) + { + if (this.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 4cad665e1..80a510ce5 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs @@ -4,73 +4,72 @@ 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 { + [ServiceManager.ServiceDependency] + private readonly ObjectTable objectTable = Service.Get(); + /// - /// 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) { - [ServiceManager.ServiceDependency] - private readonly ObjectTable objectTable = Service.Get(); - - /// - /// 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 => this.objectTable.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 => this.objectTable.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 b58da8ad5..491d2aeae 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -13,193 +13,192 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class ClientState : IDisposable, IServiceType { - /// - /// This class represents the state of the game client at the time of access. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class ClientState : IDisposable, IServiceType + private readonly ClientStateAddressResolver address; + private readonly Hook setupTerritoryTypeHook; + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly NetworkHandlers networkHandlers = Service.Get(); + + private bool lastConditionNone = true; + private bool lastFramePvP = false; + + [ServiceManager.ServiceConstructor] + private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo) { - private readonly ClientStateAddressResolver address; - private readonly Hook setupTerritoryTypeHook; + this.address = new ClientStateAddressResolver(); + this.address.Setup(sigScanner); - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); + Log.Verbose("===== C L I E N T S T A T E ====="); - [ServiceManager.ServiceDependency] - private readonly NetworkHandlers networkHandlers = Service.Get(); + this.ClientLanguage = startInfo.Language; - private bool lastConditionNone = true; - private bool lastFramePvP = false; + Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); - [ServiceManager.ServiceConstructor] - private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo) + this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); + + this.framework.Update += this.FrameworkOnOnUpdateEvent; + + this.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 fires when a character is entering PvP. + /// + public event Action EnterPvP; + + /// + /// Event that fires when a character is leaving PvP. + /// + public event Action LeavePvP; + + /// + /// 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.GetNullable()?[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; } + + /// + /// Gets a value indicating whether or not the user is playing PvP. + /// + public bool IsPvP { get; private set; } + + /// + /// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den. + /// + public bool IsPvPExcludingDen { get; private set; } + + /// + /// Gets client state address resolver. + /// + internal ClientStateAddressResolver AddressResolver => this.address; + + /// + /// Dispose of managed and unmanaged resources. + /// + void IDisposable.Dispose() + { + this.setupTerritoryTypeHook.Dispose(); + this.framework.Update -= this.FrameworkOnOnUpdateEvent; + this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.setupTerritoryTypeHook.Enable(); + } + + private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) + { + this.TerritoryType = terriType; + this.TerritoryChanged?.InvokeSafely(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?.InvokeSafely(this, e); + } + + private void FrameworkOnOnUpdateEvent(Framework framework1) + { + var condition = Service.GetNullable(); + var gameGui = Service.GetNullable(); + var data = Service.GetNullable(); + + if (condition == null || gameGui == null || data == null) + return; + + if (condition.Any() && this.lastConditionNone == true) { - this.address = new ClientStateAddressResolver(); - this.address.Setup(sigScanner); - - Log.Verbose("===== C L I E N T S T A T E ====="); - - this.ClientLanguage = startInfo.Language; - - Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); - - this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); - - this.framework.Update += this.FrameworkOnOnUpdateEvent; - - this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; + Log.Debug("Is login"); + this.lastConditionNone = false; + this.IsLoggedIn = true; + this.Login?.InvokeSafely(this, null); + gameGui.ResetUiHideState(); } - [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 fires when a character is entering PvP. - /// - public event Action EnterPvP; - - /// - /// Event that fires when a character is leaving PvP. - /// - public event Action LeavePvP; - - /// - /// 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.GetNullable()?[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; } - - /// - /// Gets a value indicating whether or not the user is playing PvP. - /// - public bool IsPvP { get; private set; } - - /// - /// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den. - /// - public bool IsPvPExcludingDen { get; private set; } - - /// - /// Gets client state address resolver. - /// - internal ClientStateAddressResolver AddressResolver => this.address; - - /// - /// Dispose of managed and unmanaged resources. - /// - void IDisposable.Dispose() + if (!condition.Any() && this.lastConditionNone == false) { - this.setupTerritoryTypeHook.Dispose(); - this.framework.Update -= this.FrameworkOnOnUpdateEvent; - this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; + Log.Debug("Is logout"); + this.lastConditionNone = true; + this.IsLoggedIn = false; + this.Logout?.InvokeSafely(this, null); + gameGui.ResetUiHideState(); } - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction() + this.IsPvP = GameMain.IsInPvPArea(); + this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; + + if (this.IsPvP != this.lastFramePvP) { - this.setupTerritoryTypeHook.Enable(); - } + this.lastFramePvP = this.IsPvP; - private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) - { - this.TerritoryType = terriType; - this.TerritoryChanged?.InvokeSafely(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?.InvokeSafely(this, e); - } - - private void FrameworkOnOnUpdateEvent(Framework framework1) - { - var condition = Service.GetNullable(); - var gameGui = Service.GetNullable(); - var data = Service.GetNullable(); - - if (condition == null || gameGui == null || data == null) - return; - - if (condition.Any() && this.lastConditionNone == true) + if (this.IsPvP) { - Log.Debug("Is login"); - this.lastConditionNone = false; - this.IsLoggedIn = true; - this.Login?.InvokeSafely(this, null); - gameGui.ResetUiHideState(); + this.EnterPvP?.InvokeSafely(); } - - if (!condition.Any() && this.lastConditionNone == false) + else { - Log.Debug("Is logout"); - this.lastConditionNone = true; - this.IsLoggedIn = false; - this.Logout?.InvokeSafely(this, null); - gameGui.ResetUiHideState(); - } - - this.IsPvP = GameMain.IsInPvPArea(); - this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; - - if (this.IsPvP != this.lastFramePvP) - { - this.lastFramePvP = this.IsPvP; - - if (this.IsPvP) - { - this.EnterPvP?.InvokeSafely(); - } - else - { - this.LeavePvP?.InvokeSafely(); - } + this.LeavePvP?.InvokeSafely(); } } } diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 4fa7feb79..98d3bc6dd 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -1,128 +1,127 @@ 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; } + + /// + /// Gets the address of the Telepo instance. + /// + public IntPtr Telepo { 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; } + + /// + /// Gets the address of the method which updates the list of available teleport locations. + /// + public IntPtr UpdateAetheryteList { 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 3D ?? ?? ?? ?? 33 ED") + 0x8; - /// - /// Gets the address of job gauge data. - /// - public IntPtr JobGaugeData { get; private set; } + this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 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; } + this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); - /// - /// Gets the address of the Telepo instance. - /// - public IntPtr Telepo { get; private set; } + this.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12"); - // 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; } - - /// - /// Gets the address of the method which updates the list of available teleport locations. - /// - public IntPtr UpdateAetheryteList { 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 3D ?? ?? ?? ?? 33 ED") + 0x8; - - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 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.Telepo = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 48 8B 12"); - - this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50"); - } + this.UpdateAetheryteList = sig.ScanText("E8 ?? ?? ?? ?? 48 89 46 68 4C 8D 45 50"); } } diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 6cf0bbdd5..8fcf59b00 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -4,152 +4,151 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed partial class Condition : IServiceType { /// - /// 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")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class Condition : IServiceType + public const int MaxConditionEntries = 100; + + private readonly bool[] cache = new bool[MaxConditionEntries]; + + [ServiceManager.ServiceConstructor] + private Condition(ClientState clientState) { - /// - /// 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; + var resolver = clientState.AddressResolver; + 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); - [ServiceManager.ServiceConstructor] - private Condition(ClientState clientState) + /// + /// 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 { - var resolver = clientState.AddressResolver; - 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; + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(Framework framework) + { + // 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; } + framework.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; - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(Framework framework) - { - // Initialization - for (var i = 0; i < MaxConditionEntries; i++) - this.cache[i] = this[i]; - - framework.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. + /// + void IDisposable.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. - /// - void IDisposable.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 7d941304c..3c68d2e43 100644 --- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs +++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs @@ -1,468 +1,467 @@ -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, - - /// - /// Cannot execute at this time. - /// - Unknown96 = 96, - - /// - /// Unable to execute command while wearing a guise. - /// - Disguised = 97, - - /// - /// Unable to execute command while recruiting for a non-cross-world party. - /// - RecruitingWorldOnly = 98, - } + 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, + + /// + /// Cannot execute at this time. + /// + Unknown96 = 96, + + /// + /// Unable to execute command while wearing a guise. + /// + Disguised = 97, + + /// + /// Unable to execute command while recruiting for a non-cross-world party. + /// + RecruitingWorldOnly = 98, } diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 1e5176a9a..440767846 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.GetNullable(); - - if (fate == null || clientState == 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.GetNullable(); + + if (fate == null || clientState == 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 93a0e60b9..dfd4bcaee 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -6,137 +6,136 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed partial class FateTable : IServiceType { - /// - /// This collection represents the currently available Fate events. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class FateTable : IServiceType + private readonly ClientStateAddressResolver address; + + [ServiceManager.ServiceConstructor] + private FateTable(ClientState clientState) { - private readonly ClientStateAddressResolver address; + this.address = clientState.AddressResolver; - [ServiceManager.ServiceConstructor] - private FateTable(ClientState clientState) + 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 = clientState.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 - if (Struct->FateDirector == null) - return 0; - - if (Struct->Fates.First == null || Struct->Fates.Last == null) - return 0; - - return (int)Struct->Fates.Size(); - } - } - - /// - /// 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; - return (IntPtr)this.Struct->Fates.Get((ulong)index).Value; - } + // Sonar used this to check if the table was safe to read + if (Struct->FateDirector == null) + 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(); + if (Struct->Fates.First == null || Struct->Fates.Last == null) + return 0; - if (clientState.LocalContentId == 0) - return null; - - if (offset == IntPtr.Zero) - return null; - - return new Fate(offset); + return (int)Struct->Fates.Size(); } } /// - /// 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; + + return (IntPtr)this.Struct->Fates.Get((ulong)index).Value; + } + + /// + /// 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 bd4cadbc8..c72e9c1de 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -6,249 +6,248 @@ using Dalamud.IoC.Internal; 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. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[ServiceManager.BlockingEarlyLoadedService] +public unsafe class GamepadState : IDisposable, IServiceType { - /// - /// Exposes the game gamepad state to dalamud. - /// - /// Will block game's gamepad input if is set. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public unsafe class GamepadState : IDisposable, IServiceType + private readonly Hook gamepadPoll; + + private bool isDisposed; + + private int leftStickX; + private int leftStickY; + private int rightStickX; + private int rightStickY; + + [ServiceManager.ServiceConstructor] + private GamepadState(ClientState clientState) { - private readonly Hook gamepadPoll; + var resolver = clientState.AddressResolver; + Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); + this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour); + } - private bool isDisposed; + private delegate int ControllerPoll(IntPtr controllerInput); - private int leftStickX; - private int leftStickY; - private int rightStickX; - private int rightStickY; + /// + /// Gets the pointer to the current instance of the GamepadInput struct. + /// + public IntPtr GamepadInputAddress { get; private set; } - [ServiceManager.ServiceConstructor] - private GamepadState(ClientState clientState) + /// + /// 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; + + /// + /// Disposes this instance, alongside its hooks. + /// + void IDisposable.Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.gamepadPoll.Enable(); + } + + private int GamepadPollDetour(IntPtr gamepadInput) + { + var original = this.gamepadPoll.Original(gamepadInput); + try { - var resolver = clientState.AddressResolver; - Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); - this.gamepadPoll = Hook.FromAddress(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; - 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; - - /// - /// Disposes this instance, alongside its hooks. - /// - void IDisposable.Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction() - { - this.gamepadPoll.Enable(); - } - - 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/BeastChakra.cs b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs index 28d34d55e..9c4d40700 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// MNK Beast Chakra types. +/// +public enum BeastChakra : byte { /// - /// MNK Beast Chakra types. + /// No card. /// - public enum BeastChakra : byte - { - /// - /// No card. - /// - NONE = 0, + NONE = 0, - /// - /// The Coeurl chakra. - /// - COEURL = 1, + /// + /// The Coeurl chakra. + /// + COEURL = 1, - /// - /// The Opo-Opo chakra. - /// - OPOOPO = 2, + /// + /// The Opo-Opo chakra. + /// + OPOOPO = 2, - /// - /// The Raptor chakra. - /// - RAPTOR = 3, - } + /// + /// The Raptor chakra. + /// + RAPTOR = 3, } 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/Kaeshi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs index 5f41dde48..e35dcc7f9 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs @@ -1,33 +1,32 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// SAM Kaeshi types. +/// +public enum Kaeshi : byte { /// - /// SAM Kaeshi types. + /// No Kaeshi is active. /// - public enum Kaeshi : byte - { - /// - /// No Kaeshi is active. - /// - NONE = 0, + NONE = 0, - /// - /// Kaeshi: Higanbana type. - /// - HIGANBANA = 1, + /// + /// Kaeshi: Higanbana type. + /// + HIGANBANA = 1, - /// - /// Kaeshi: Goken type. - /// - GOKEN = 2, + /// + /// Kaeshi: Goken type. + /// + GOKEN = 2, - /// - /// Kaeshi: Setsugekka type. - /// - SETSUGEKKA = 3, + /// + /// Kaeshi: Setsugekka type. + /// + SETSUGEKKA = 3, - /// - /// Kaeshi: Namikiri type. - /// - NAMIKIRI = 4, - } + /// + /// Kaeshi: Namikiri type. + /// + NAMIKIRI = 4, } 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/Nadi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs index f84a7e55e..159ecbb26 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs @@ -1,26 +1,25 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// MNK Nadi types. +/// +[Flags] +public enum Nadi : byte { /// - /// MNK Nadi types. + /// No card. /// - [Flags] - public enum Nadi : byte - { - /// - /// No card. - /// - NONE = 0, + NONE = 0, - /// - /// The Lunar nadi. - /// - LUNAR = 2, + /// + /// The Lunar nadi. + /// + LUNAR = 2, - /// - /// The Solar nadi. - /// - SOLAR = 4, - } + /// + /// The Solar nadi. + /// + SOLAR = 4, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs index 7aaaca908..248caa396 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs @@ -1,48 +1,47 @@ -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, - /// - /// Normal carbuncle pet glam. - /// - CARBUNCLE = 4, + /// + /// Normal carbuncle pet glam. + /// + CARBUNCLE = 4, - /// - /// Ifrit Egi pet glam. - /// - IFRIT = 5, + /// + /// Ifrit Egi pet glam. + /// + IFRIT = 5, - /// - /// Titan Egi pet glam. - /// - TITAN = 6, + /// + /// Titan Egi pet glam. + /// + TITAN = 6, - /// - /// Garuda Egi pet glam. - /// - GARUDA = 7, - } + /// + /// Garuda Egi pet glam. + /// + GARUDA = 7, } 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 b2440640e..c23fbbbff 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 = 1, + /// + /// Mage's Ballad type. + /// + MAGE = 1, - /// - /// Army's Paeon type. - /// - ARMY = 2, + /// + /// Army's Paeon type. + /// + ARMY = 2, - /// - /// The Wanderer's Minuet type. - /// - WANDERER = 3, - } + /// + /// The Wanderer's Minuet type. + /// + WANDERER = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs index aa7e7c8a2..30cc0fff0 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs @@ -1,18 +1,17 @@ -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 Carbuncle. - /// - CARBUNCLE = 23, - } + /// + /// The summoned pet Carbuncle. + /// + CARBUNCLE = 23, } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index aab5d4991..bf5c4b525 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -7,46 +7,45 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public class JobGauges : IServiceType { - /// - /// This class converts in-memory Job gauge data to structs. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public class JobGauges : IServiceType + private Dictionary cache = new(); + + [ServiceManager.ServiceConstructor] + private JobGauges(ClientState clientState) { - private Dictionary cache = new(); + this.Address = clientState.AddressResolver.JobGaugeData; - [ServiceManager.ServiceConstructor] - private JobGauges(ClientState clientState) + 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 = clientState.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 cb8ed1eb0..4549ff9c9 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs @@ -3,44 +3,43 @@ using System.Linq; 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 & 0xF); - - /// - /// Gets the currently drawn crown . - /// - /// Currently drawn crown . - public CardType DrawnCrownCard => this.Struct->Card - this.DrawnCard; - - /// - /// Gets the s currently active. - /// - public SealType[] Seals => this.Struct->CurrentSeals.Select(seal => (SealType)seal).ToArray(); - - /// - /// 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) => this.Seals.Contains(seal); } + + /// + /// Gets the currently drawn . + /// + /// Currently drawn . + public CardType DrawnCard => (CardType)(this.Struct->Card & 0xF); + + /// + /// Gets the currently drawn crown . + /// + /// Currently drawn crown . + public CardType DrawnCrownCard => this.Struct->Card - this.DrawnCard; + + /// + /// Gets the s currently active. + /// + public SealType[] Seals => this.Struct->CurrentSeals.Select(seal => (SealType)seal).ToArray(); + + /// + /// 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) => this.Seals.Contains(seal); } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs index e195225d8..4ed8eabe4 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs @@ -1,73 +1,72 @@ 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 or not the player is in Umbral Ice. - /// - /// true or false. - public bool InUmbralIce => this.Struct->ElementStance < 0; - - /// - /// Gets a value indicating whether or not the player is in Astral fire. - /// - /// true or false. - public bool InAstralFire => this.Struct->ElementStance > 0; - - /// - /// Gets a value indicating whether or not Enochian is active. - /// - /// true or false. - public bool IsEnochianActive => this.Struct->EnochianActive; - - /// - /// Gets a value indicating whether Paradox is active. - /// - /// true or false. - public bool IsParadoxActive => this.Struct->ParadoxActive; } + + /// + /// 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 or not the player is in Umbral Ice. + /// + /// true or false. + public bool InUmbralIce => this.Struct->ElementStance < 0; + + /// + /// Gets a value indicating whether or not the player is in Astral fire. + /// + /// true or false. + public bool InAstralFire => this.Struct->ElementStance > 0; + + /// + /// Gets a value indicating whether or not Enochian is active. + /// + /// true or false. + public bool IsEnochianActive => this.Struct->EnochianActive; + + /// + /// Gets a value indicating whether Paradox is active. + /// + /// true or false. + public bool IsParadoxActive => this.Struct->ParadoxActive; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs index 006ee907b..1a7f7bd47 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs @@ -3,94 +3,93 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; using FFXIVClientStructs.FFXIV.Client.Game.Gauge; -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 ushort 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 + { + get { + if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet)) + return Song.WANDERER; + + if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon)) + return Song.ARMY; + + if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad)) + return Song.MAGE; + + return Song.NONE; } + } - /// - /// Gets the current song timer in milliseconds. - /// - public ushort 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 + /// + /// Gets the type of song that was last played. + /// + public Song LastSong + { + get { - get - { - if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuet)) - return Song.WANDERER; + if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed)) + return Song.WANDERER; - if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon)) - return Song.ARMY; + if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed)) + return Song.ARMY; - if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad)) - return Song.MAGE; + if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed)) + return Song.MAGE; - return Song.NONE; - } + return Song.NONE; } + } - /// - /// Gets the type of song that was last played. - /// - public Song LastSong + /// + /// Gets the song Coda that are currently active. + /// + /// + /// This will always return an array of size 3, inactive Coda are represented by . + /// + public Song[] Coda + { + get { - get + return new[] { - if (this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed)) - return Song.WANDERER; - - if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed)) - return Song.ARMY; - - if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed)) - return Song.MAGE; - - return Song.NONE; - } - } - - /// - /// Gets the song Coda that are currently active. - /// - /// - /// This will always return an array of size 3, inactive Coda are represented by . - /// - public Song[] Coda - { - get - { - return new[] - { - this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE, - this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE, - this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE, - }; - } + this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE, + this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE, + this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE, + }; } } } 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 1003d2cd5..39e39b41b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs @@ -1,39 +1,38 @@ using System; -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 Life of the Dragon in milliseconds. - /// - public short LOTDTimer => this.Struct->LotdTimer; - - /// - /// Gets a value indicating whether Life of the Dragon is active. - /// - public bool IsLOTDActive => this.Struct->LotdState == 2; - - /// - /// Gets the count of eyes opened during Blood of the Dragon. - /// - public byte EyeCount => this.Struct->EyeCount; - - /// - /// Gets the amount of Firstminds' Focus available. - /// - public byte FirstmindsFocusCount => this.Struct->FirstmindsFocusCount; } + + /// + /// Gets the time remaining for Life of the Dragon in milliseconds. + /// + public short LOTDTimer => this.Struct->LotdTimer; + + /// + /// Gets a value indicating whether Life of the Dragon is active. + /// + public bool IsLOTDActive => this.Struct->LotdState == 2; + + /// + /// Gets the count of eyes opened during Blood of the Dragon. + /// + public byte EyeCount => this.Struct->EyeCount; + + /// + /// Gets the amount of Firstminds' Focus available. + /// + public byte FirstmindsFocusCount => this.Struct->FirstmindsFocusCount; } 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 752e0dc42..4f572a7c4 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs @@ -3,43 +3,42 @@ using System.Linq; using Dalamud.Game.ClientState.JobGauge.Enums; -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 amount of Chakra available. - /// - public byte Chakra => this.Struct->Chakra; - - /// - /// Gets the types of Beast Chakra available. - /// - /// - /// This will always return an array of size 3, inactive Beast Chakra are represented by . - /// - public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray(); - - /// - /// Gets the types of Nadi available. - /// - public Nadi Nadi => (Nadi)this.Struct->Nadi; - - /// - /// Gets the time remaining that Blitz is active. - /// - public ushort BlitzTimeRemaining => this.Struct->BlitzTimeRemaining; } + + /// + /// Gets the amount of Chakra available. + /// + public byte Chakra => this.Struct->Chakra; + + /// + /// Gets the types of Beast Chakra available. + /// + /// + /// This will always return an array of size 3, inactive Beast Chakra are represented by . + /// + public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray(); + + /// + /// Gets the types of Nadi available. + /// + public Nadi Nadi => (Nadi)this.Struct->Nadi; + + /// + /// Gets the time remaining that Blitz is active. + /// + public ushort BlitzTimeRemaining => this.Struct->BlitzTimeRemaining; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs index 40bf64d4a..998ee10a4 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 af16ee031..0b10a2b4d 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs @@ -1,34 +1,33 @@ 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 amount of mana stacks. - /// - public byte ManaStacks => this.Struct->ManaStacks; } + + /// + /// 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 amount of mana stacks. + /// + public byte ManaStacks => this.Struct->ManaStacks; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/RPRGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/RPRGauge.cs index f08d0bbe0..7ddafcc4c 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/RPRGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/RPRGauge.cs @@ -1,44 +1,43 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory RPR job gauge. +/// +public unsafe class RPRGauge : JobGaugeBase { /// - /// In-memory RPR job gauge. + /// Initializes a new instance of the class. /// - public unsafe class RPRGauge : JobGaugeBase + /// Address of the job gauge. + internal RPRGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal RPRGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the amount of Soul available. - /// - public byte Soul => this.Struct->Soul; - - /// - /// Gets the amount of Shroud available. - /// - public byte Shroud => this.Struct->Shroud; - - /// - /// Gets the time remaining that Enshrouded is active. - /// - public ushort EnshroudedTimeRemaining => this.Struct->EnshroudedTimeRemaining; - - /// - /// Gets the amount of Lemure Shroud available. - /// - public byte LemureShroud => this.Struct->LemureShroud; - - /// - /// Gets the amount of Void Shroud available. - /// - public byte VoidShroud => this.Struct->VoidShroud; } + + /// + /// Gets the amount of Soul available. + /// + public byte Soul => this.Struct->Soul; + + /// + /// Gets the amount of Shroud available. + /// + public byte Shroud => this.Struct->Shroud; + + /// + /// Gets the time remaining that Enshrouded is active. + /// + public ushort EnshroudedTimeRemaining => this.Struct->EnshroudedTimeRemaining; + + /// + /// Gets the amount of Lemure Shroud available. + /// + public byte LemureShroud => this.Struct->LemureShroud; + + /// + /// Gets the amount of Void Shroud available. + /// + public byte VoidShroud => this.Struct->VoidShroud; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs index 242e3f9f0..4d4c2d397 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs @@ -2,58 +2,57 @@ 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 currently active Kaeshi ability. - /// - public Kaeshi Kaeshi => (Kaeshi)this.Struct->Kaeshi; - - /// - /// 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 currently active Kaeshi ability. + /// + public Kaeshi Kaeshi => (Kaeshi)this.Struct->Kaeshi; + + /// + /// 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 cadeeb0ba..ae8c87f3a 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 remaining time Seraph is active 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 remaining time Seraph is active 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/SGEGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SGEGauge.cs index e1a09bf7b..4775809c1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SGEGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SGEGauge.cs @@ -1,40 +1,39 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory SGE job gauge. +/// +public unsafe class SGEGauge : JobGaugeBase { /// - /// In-memory SGE job gauge. + /// Initializes a new instance of the class. /// - public unsafe class SGEGauge : JobGaugeBase + /// Address of the job gauge. + internal SGEGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal SGEGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the amount of milliseconds elapsed until the next Addersgall is available. - /// This counts from 0 to 20_000. - /// - public short AddersgallTimer => this.Struct->AddersgallTimer; - - /// - /// Gets the amount of Addersgall available. - /// - public byte Addersgall => this.Struct->Addersgall; - - /// - /// Gets the amount of Addersting available. - /// - public byte Addersting => this.Struct->Addersting; - - /// - /// Gets a value indicating whether Eukrasia is activated. - /// - public bool Eukrasia => this.Struct->Eukrasia == 1; } + + /// + /// Gets the amount of milliseconds elapsed until the next Addersgall is available. + /// This counts from 0 to 20_000. + /// + public short AddersgallTimer => this.Struct->AddersgallTimer; + + /// + /// Gets the amount of Addersgall available. + /// + public byte Addersgall => this.Struct->Addersgall; + + /// + /// Gets the amount of Addersting available. + /// + public byte Addersting => this.Struct->Addersting; + + /// + /// Gets a value indicating whether Eukrasia is activated. + /// + public bool Eukrasia => this.Struct->Eukrasia == 1; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index c567c590b..6b03a4c6e 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -3,112 +3,111 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; using FFXIVClientStructs.FFXIV.Client.Game.Gauge; -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 ushort SummonTimerRemaining => this.Struct->SummonTimer; - - /// - /// Gets the time remaining for the current attunement. - /// - public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer; - - /// - /// Gets the summon that will return after the current summon expires. - /// This maps to the sheet. - /// - public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; - - /// - /// Gets the summon glam for the . - /// This maps to the sheet. - /// - public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; - - /// - /// Gets the amount of aspected Attunment remaining. - /// - public byte Attunement => this.Struct->Attunement; - - /// - /// Gets the current aether flags. - /// Use the summon accessors instead. - /// - public AetherFlags AetherFlags => this.Struct->AetherFlags; - - /// - /// Gets a value indicating whether Bahamut is ready to be summoned. - /// - /// true or false. - public bool IsBahamutReady => !this.AetherFlags.HasFlag(AetherFlags.PhoenixReady); - - /// - /// Gets a value indicating whether if Phoenix is ready to be summoned. - /// - /// true or false. - public bool IsPhoenixReady => this.AetherFlags.HasFlag(AetherFlags.PhoenixReady); - - /// - /// Gets a value indicating whether if Ifrit is ready to be summoned. - /// - /// true or false. - public bool IsIfritReady => this.AetherFlags.HasFlag(AetherFlags.IfritReady); - - /// - /// Gets a value indicating whether if Titan is ready to be summoned. - /// - /// true or false. - public bool IsTitanReady => this.AetherFlags.HasFlag(AetherFlags.TitanReady); - - /// - /// Gets a value indicating whether if Garuda is ready to be summoned. - /// - /// true or false. - public bool IsGarudaReady => this.AetherFlags.HasFlag(AetherFlags.GarudaReady); - - /// - /// Gets a value indicating whether if Ifrit is currently attuned. - /// - /// true or false. - public bool IsIfritAttuned => this.AetherFlags.HasFlag(AetherFlags.IfritAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); - - /// - /// Gets a value indicating whether if Titan is currently attuned. - /// - /// true or false. - public bool IsTitanAttuned => this.AetherFlags.HasFlag(AetherFlags.TitanAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); - - /// - /// Gets a value indicating whether if Garuda is currently attuned. - /// - /// true or false. - public bool IsGarudaAttuned => this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); - - /// - /// Gets a value indicating whether there are any Aetherflow stacks available. - /// - /// true or false. - public bool HasAetherflowStacks => this.AetherflowStacks > 0; - - /// - /// Gets the amount of Aetherflow available. - /// - public byte AetherflowStacks => (byte)(this.AetherFlags & AetherFlags.Aetherflow); } + + /// + /// Gets the time remaining for the current summon. + /// + public ushort SummonTimerRemaining => this.Struct->SummonTimer; + + /// + /// Gets the time remaining for the current attunement. + /// + public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer; + + /// + /// Gets the summon that will return after the current summon expires. + /// This maps to the sheet. + /// + public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; + + /// + /// Gets the summon glam for the . + /// This maps to the sheet. + /// + public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; + + /// + /// Gets the amount of aspected Attunment remaining. + /// + public byte Attunement => this.Struct->Attunement; + + /// + /// Gets the current aether flags. + /// Use the summon accessors instead. + /// + public AetherFlags AetherFlags => this.Struct->AetherFlags; + + /// + /// Gets a value indicating whether Bahamut is ready to be summoned. + /// + /// true or false. + public bool IsBahamutReady => !this.AetherFlags.HasFlag(AetherFlags.PhoenixReady); + + /// + /// Gets a value indicating whether if Phoenix is ready to be summoned. + /// + /// true or false. + public bool IsPhoenixReady => this.AetherFlags.HasFlag(AetherFlags.PhoenixReady); + + /// + /// Gets a value indicating whether if Ifrit is ready to be summoned. + /// + /// true or false. + public bool IsIfritReady => this.AetherFlags.HasFlag(AetherFlags.IfritReady); + + /// + /// Gets a value indicating whether if Titan is ready to be summoned. + /// + /// true or false. + public bool IsTitanReady => this.AetherFlags.HasFlag(AetherFlags.TitanReady); + + /// + /// Gets a value indicating whether if Garuda is ready to be summoned. + /// + /// true or false. + public bool IsGarudaReady => this.AetherFlags.HasFlag(AetherFlags.GarudaReady); + + /// + /// Gets a value indicating whether if Ifrit is currently attuned. + /// + /// true or false. + public bool IsIfritAttuned => this.AetherFlags.HasFlag(AetherFlags.IfritAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + + /// + /// Gets a value indicating whether if Titan is currently attuned. + /// + /// true or false. + public bool IsTitanAttuned => this.AetherFlags.HasFlag(AetherFlags.TitanAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + + /// + /// Gets a value indicating whether if Garuda is currently attuned. + /// + /// true or false. + public bool IsGarudaAttuned => this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + + /// + /// Gets a value indicating whether there are any Aetherflow stacks available. + /// + /// true or false. + public bool HasAetherflowStacks => this.AetherflowStacks > 0; + + /// + /// Gets the amount of Aetherflow available. + /// + public byte AetherflowStacks => (byte)(this.AetherFlags & AetherFlags.Aetherflow); } 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 953f01f75..685973e17 100644 --- a/Dalamud/Game/ClientState/Keys/KeyState.cs +++ b/Dalamud/Game/ClientState/Keys/KeyState.cs @@ -6,153 +6,152 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public class KeyState : IServiceType { - /// - /// 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")] - [ServiceManager.BlockingEarlyLoadedService] - public class KeyState : IServiceType + // 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; + + [ServiceManager.ServiceConstructor] + private KeyState(SigScanner sigScanner, ClientState clientState) { - // 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 = sigScanner.Module.BaseAddress; + var addressResolver = clientState.AddressResolver; + this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); + this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); - [ServiceManager.ServiceConstructor] - private KeyState(SigScanner sigScanner, ClientState clientState) + 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 = sigScanner.Module.BaseAddress; - var addressResolver = clientState.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()) - { - 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 dd9009601..bcd410f17 100644 --- a/Dalamud/Game/ClientState/Keys/VirtualKey.cs +++ b/Dalamud/Game/ClientState/Keys/VirtualKey.cs @@ -1,1247 +1,1246 @@ -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. + /// + [VirtualKey("No key")] + NO_KEY = 0, + + /// + /// Left mouse button. + /// + [VirtualKey("Left mouse button")] + LBUTTON = 1, + + /// + /// Right mouse button. + /// + [VirtualKey("Right mouse button")] + RBUTTON = 2, + + /// + /// Control-break processing. + /// + [VirtualKey("CANCEL")] + 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. - /// - [VirtualKey("No key")] - NO_KEY = 0, - - /// - /// Left mouse button. - /// - [VirtualKey("Left mouse button")] - LBUTTON = 1, - - /// - /// Right mouse button. - /// - [VirtualKey("Right mouse button")] - RBUTTON = 2, - - /// - /// Control-break processing. - /// - [VirtualKey("CANCEL")] - CANCEL = 3, - - /// - /// Middle mouse button (three-button mouse). - /// - /// - /// NOT contiguous with L and R buttons. - /// - [VirtualKey("Mouse 3")] - MBUTTON = 4, - - /// - /// X1 mouse button. - /// - - /// - /// NOT contiguous with L and R buttons. - /// - [VirtualKey("Mouse 4")] - XBUTTON1 = 5, - - /// - /// X2 mouse button. - /// - - /// - /// NOT contiguous with L and R buttons. - /// - [VirtualKey("Mouse 5")] - XBUTTON2 = 6, - - /// - /// BACKSPACE key. - /// - [VirtualKey("Backspace")] - BACK = 8, - - /// - /// TAB key. - /// - [VirtualKey("Tab")] - TAB = 9, - - /// - /// CLEAR key. - /// - [VirtualKey("Clear")] - CLEAR = 12, - - /// - /// RETURN key. - /// - [VirtualKey("Return/Enter")] - RETURN = 13, - - /// - /// SHIFT key. - /// - [VirtualKey("Shift")] - SHIFT = 16, - - /// - /// CONTROL key. - /// - [VirtualKey("Control")] - CONTROL = 17, - - /// - /// ALT key. - /// - [VirtualKey("Alt")] - MENU = 18, - - /// - /// PAUSE key. - /// - [VirtualKey("Pause")] - PAUSE = 19, - - /// - /// CAPS LOCK key. - /// - [VirtualKey("Caps Lock")] - CAPITAL = 20, - - /// - /// IME Kana mode. - /// - [VirtualKey("Kana Key")] - KANA = 21, - - /// - /// IME Hangeul mode (maintained for compatibility; use User32.VirtualKey.HANGUL). - /// - [VirtualKey("Hangul Key")] - HANGEUL = KANA, - - /// - /// IME Hangul mode. - /// - [VirtualKey("Hangul Key 2")] - HANGUL = KANA, - - /// - /// IME Junja mode. - /// - [VirtualKey("Junja Key")] - JUNJA = 23, - - /// - /// IME final mode. - /// - [VirtualKey("Final Key")] - FINAL = 24, - - /// - /// IME Hanja mode. - /// - [VirtualKey("Hanja Key")] - HANJA = 25, - - /// - /// IME Kanji mode. - /// - [VirtualKey("Kanji Key")] - KANJI = HANJA, - - /// - /// ESC key. - /// - [VirtualKey("Escape")] - ESCAPE = 27, - - /// - /// IME convert. - /// - [VirtualKey("Convert Key")] - CONVERT = 28, - - /// - /// IME nonconvert. - /// - [VirtualKey("Non-Convert Key")] - NONCONVERT = 29, - - /// - /// IME accept. - /// - [VirtualKey("IME Accept Key")] - ACCEPT = 30, - - /// - /// IME mode change request. - /// - [VirtualKey("IME Mode-Change Key")] - MODECHANGE = 31, - - /// - /// SPACEBAR. - /// - [VirtualKey("Spacebar")] - SPACE = 32, - - /// - /// PAGE UP key. - /// - [VirtualKey("Page Up")] - PRIOR = 33, - - /// - /// PAGE DOWN key. - /// - [VirtualKey("Page Down")] - NEXT = 34, - - /// - /// END key. - /// - [VirtualKey("End")] - END = 35, - - /// - /// HOME key. - /// - [VirtualKey("Home")] - HOME = 36, - - /// - /// LEFT ARROW key. - /// - [VirtualKey("Left Arrow")] - LEFT = 37, - - /// - /// UP ARROW key. - /// - [VirtualKey("Up Arrow")] - UP = 38, - - /// - /// RIGHT ARROW key. - /// - [VirtualKey("Right Arrow")] - RIGHT = 39, - - /// - /// DOWN ARROW key. - /// - [VirtualKey("Down Arrow")] - DOWN = 40, - - /// - /// SELECT key. - /// - [VirtualKey("Select")] - SELECT = 41, - - /// - /// PRINT key. - /// - [VirtualKey("Print")] - PRINT = 42, - - /// - /// EXECUTE key. - /// - [VirtualKey("Execute")] - EXECUTE = 43, - - /// - /// PRINT SCREEN key. - /// - [VirtualKey("Print Screen")] - SNAPSHOT = 44, - - /// - /// INS key. - /// - [VirtualKey("Insert")] - INSERT = 45, - - /// - /// DEL key. - /// - [VirtualKey("Delete")] - DELETE = 46, - - /// - /// HELP key. - /// - [VirtualKey("Help")] - HELP = 47, - - /// - /// 0 key. - /// - [VirtualKey("Number-Row 0")] - KEY_0 = 48, - - /// - /// 1 key. - /// - [VirtualKey("Number-Row 1")] - KEY_1 = 49, - - /// - /// 2 key. - /// - [VirtualKey("Number-Row 2")] - KEY_2 = 50, - - /// - /// 3 key. - /// - [VirtualKey("Number-Row 3")] - KEY_3 = 51, - - /// - /// 4 key. - /// - [VirtualKey("Number-Row 4")] - KEY_4 = 52, - - /// - /// 5 key. - /// - [VirtualKey("Number-Row 5")] - KEY_5 = 53, - - /// - /// 6 key. - /// - [VirtualKey("Number-Row 6")] - KEY_6 = 54, - - /// - /// 7 key. - /// - [VirtualKey("Number-Row 7")] - KEY_7 = 55, - - /// - /// 8 key. - /// - [VirtualKey("Number-Row 8")] - KEY_8 = 56, - - /// - /// 9 key. - /// - [VirtualKey("Number-Row 9")] - KEY_9 = 57, - - /// - /// A key. - /// - [VirtualKey("A")] - A = 65, - - /// - /// B key. - /// - [VirtualKey("B")] - B = 66, - - /// - /// C key. - /// - [VirtualKey("C")] - C = 67, - - /// - /// D key. - /// - [VirtualKey("D")] - D = 68, - - /// - /// E key. - /// - [VirtualKey("E")] - E = 69, - - /// - /// F key. - /// - [VirtualKey("F")] - F = 70, - - /// - /// G key. - /// - [VirtualKey("G")] - G = 71, - - /// - /// H key. - /// - [VirtualKey("H")] - H = 72, - - /// - /// I key. - /// - [VirtualKey("I")] - I = 73, - - /// - /// J key. - /// - [VirtualKey("J")] - J = 74, - - /// - /// K key. - /// - [VirtualKey("K")] - K = 75, - - /// - /// L key. - /// - [VirtualKey("L")] - L = 76, - - /// - /// M key. - /// - [VirtualKey("M")] - M = 77, - - /// - /// N key. - /// - [VirtualKey("N")] - N = 78, - - /// - /// O key. - /// - [VirtualKey("O")] - O = 79, - - /// - /// P key. - /// - [VirtualKey("P")] - P = 80, - - /// - /// Q key. - /// - [VirtualKey("Q")] - Q = 81, - - /// - /// R key. - /// - [VirtualKey("R")] - R = 82, - - /// - /// S key. - /// - [VirtualKey("S")] - S = 83, - - /// - /// T key. - /// - [VirtualKey("T")] - T = 84, - - /// - /// U key. - /// - [VirtualKey("U")] - U = 85, - - /// - /// V key. - /// - [VirtualKey("V")] - V = 86, - - /// - /// W key. - /// - [VirtualKey("W")] - W = 87, - - /// - /// X key. - /// - [VirtualKey("X")] - X = 88, - - /// - /// Y key. - /// - [VirtualKey("Y")] - Y = 89, - - /// - /// Z key. - /// - [VirtualKey("Z")] - Z = 90, - - /// - /// Left Windows key (Natural keyboard). - /// - [VirtualKey("Left Windows")] - LWIN = 91, - - /// - /// Right Windows key (Natural keyboard). - /// - [VirtualKey("Right Windows")] - RWIN = 92, - - /// - /// Applications key (Natural keyboard). - /// - [VirtualKey("Applications")] - APPS = 93, - - /// - /// Computer Sleep key. - /// - [VirtualKey("Sleep")] - SLEEP = 95, - - /// - /// Numeric keypad 0 key. - /// - [VirtualKey("Numpad 0")] - NUMPAD0 = 96, - - /// - /// Numeric keypad 1 key. - /// - [VirtualKey("Numpad 1")] - NUMPAD1 = 97, - - /// - /// Numeric keypad 2 key. - /// - [VirtualKey("Numpad 2")] - NUMPAD2 = 98, - - /// - /// Numeric keypad 3 key. - /// - [VirtualKey("Numpad 3")] - NUMPAD3 = 99, - - /// - /// Numeric keypad 4 key. - /// - [VirtualKey("Numpad 4")] - NUMPAD4 = 100, - - /// - /// Numeric keypad 5 key. - /// - [VirtualKey("Numpad 5")] - NUMPAD5 = 101, - - /// - /// Numeric keypad 6 key. - /// - [VirtualKey("Numpad 6")] - NUMPAD6 = 102, - - /// - /// Numeric keypad 7 key. - /// - [VirtualKey("Numpad 7")] - NUMPAD7 = 103, - - /// - /// Numeric keypad 8 key. - /// - [VirtualKey("Numpad 8")] - NUMPAD8 = 104, - - /// - /// Numeric keypad 9 key. - /// - [VirtualKey("Numpad 9")] - NUMPAD9 = 105, - - /// - /// Multiply key. - /// - [VirtualKey("Numpad Multiply")] - MULTIPLY = 106, - - /// - /// Add key. - /// - [VirtualKey("Numpad Add")] - ADD = 107, - - /// - /// Separator key. - /// - [VirtualKey("Numpad Separator")] - SEPARATOR = 108, - - /// - /// Subtract key. - /// - [VirtualKey("Numpad Subtract")] - SUBTRACT = 109, - - /// - /// Decimal key. - /// - [VirtualKey("Numpad Decimal")] - DECIMAL = 110, - - /// - /// Divide key. - /// - [VirtualKey("Numpad Divide")] - DIVIDE = 111, - - /// - /// F1 Key. - /// - [VirtualKey("F1")] - F1 = 112, - - /// - /// F2 Key. - /// - [VirtualKey("F2")] - F2 = 113, - - /// - /// F3 Key. - /// - [VirtualKey("F3")] - F3 = 114, - - /// - /// F4 Key. - /// - [VirtualKey("F4")] - F4 = 115, - - /// - /// F5 Key. - /// - [VirtualKey("F5")] - F5 = 116, - - /// - /// F6 Key. - /// - [VirtualKey("F6")] - F6 = 117, - - /// - /// F7 Key. - /// - [VirtualKey("F7")] - F7 = 118, - - /// - /// F8 Key. - /// - [VirtualKey("F8")] - F8 = 119, - - /// - /// F9 Key. - /// - [VirtualKey("F9")] - F9 = 120, - - /// - /// F10 Key. - /// - [VirtualKey("F10")] - F10 = 121, - - /// - /// F11 Key. - /// - [VirtualKey("F11")] - F11 = 122, - - /// - /// F12 Key. - /// - [VirtualKey("F12")] - F12 = 123, - - /// - /// F13 Key. - /// - [VirtualKey("F13")] - F13 = 124, - - /// - /// F14 Key. - /// - [VirtualKey("F14")] - F14 = 125, - - /// - /// F15 Key. - /// - [VirtualKey("F15")] - F15 = 126, - - /// - /// F16 Key. - /// - [VirtualKey("F16")] - F16 = 127, - - /// - /// F17 Key. - /// - [VirtualKey("F17")] - F17 = 128, - - /// - /// F18 Key. - /// - [VirtualKey("F18")] - F18 = 129, - - /// - /// F19 Key. - /// - [VirtualKey("F19")] - F19 = 130, - - /// - /// F20 Key. - /// - [VirtualKey("F20")] - F20 = 131, - - /// - /// F21 Key. - /// - [VirtualKey("F21")] - F21 = 132, - - /// - /// F22 Key. - /// - [VirtualKey("F22")] - F22 = 133, - - /// - /// F23 Key. - /// - [VirtualKey("F23")] - F23 = 134, - - /// - /// F24 Key. - /// - [VirtualKey("F24")] - F24 = 135, - - /// - /// NUM LOCK key. - /// - [VirtualKey("Num-Lock")] - NUMLOCK = 144, - - /// - /// SCROLL LOCK key. - /// - [VirtualKey("Scroll-Lock")] - SCROLL = 145, - - /// - /// '=' key on numpad (NEC PC-9800 kbd definitions). - /// - [VirtualKey("Numpad Equals")] - OEM_NEC_EQUAL = 146, - - /// - /// 'Dictionary' key (Fujitsu/OASYS kbd definitions). - /// - [VirtualKey("Dictionary (Fujitsu)")] - OEM_FJ_JISHO = OEM_NEC_EQUAL, - - /// - /// 'Unregister word' key (Fujitsu/OASYS kbd definitions). - /// - [VirtualKey("Unregister word (Fujitsu)")] - OEM_FJ_MASSHOU = 147, - - /// - /// 'Register word' key (Fujitsu/OASYS kbd definitions). - /// - [VirtualKey("Register word (Fujitsu)")] - OEM_FJ_TOUROKU = 148, - - /// - /// 'Left OYAYUBI' key (Fujitsu/OASYS kbd definitions). - /// - [VirtualKey("Left Oyayubi (Fujitsu)")] - OEM_FJ_LOYA = 149, - - /// - /// 'Right OYAYUBI' key (Fujitsu/OASYS kbd definitions). - /// - [VirtualKey("Right Oyayubi (Fujitsu)")] - 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. - /// - [VirtualKey("Left Shift")] - LSHIFT = 160, - - /// - /// Right SHIFT key. - /// - [VirtualKey("Right Shift")] - RSHIFT = 161, - - /// - /// Left CONTROL key. - /// - [VirtualKey("Left Control")] - LCONTROL = 162, - - /// - /// Right CONTROL key. - /// - [VirtualKey("Right Control")] - RCONTROL = 163, - - /// - /// Left MENU key. - /// - [VirtualKey("Left Menu")] - LMENU = 164, - - /// - /// Right MENU key. - /// - [VirtualKey("Right Menu")] - RMENU = 165, - - /// - /// Browser Back key. - /// - [VirtualKey("Browser Back")] - BROWSER_BACK = 166, - - /// - /// Browser Forward key. - /// - [VirtualKey("Browser Forward")] - BROWSER_FORWARD = 167, - - /// - /// Browser Refresh key. - /// - [VirtualKey("Browser Refresh")] - BROWSER_REFRESH = 168, - - /// - /// Browser Stop key. - /// - [VirtualKey("Browser Stop")] - BROWSER_STOP = 169, - - /// - /// Browser Search key. - /// - [VirtualKey("Browser Search")] - BROWSER_SEARCH = 170, - - /// - /// Browser Favorites key. - /// - [VirtualKey("Browser Favorites")] - BROWSER_FAVORITES = 171, - - /// - /// Browser Start and Home key. - /// - [VirtualKey("Browser Home")] - BROWSER_HOME = 172, - - /// - /// Volume Mute key. - /// - [VirtualKey("Mute Volume")] - VOLUME_MUTE = 173, - - /// - /// Volume Down key. - /// - [VirtualKey("Volume Down")] - VOLUME_DOWN = 174, - - /// - /// Volume Up key. - /// - [VirtualKey("Volume Up")] - VOLUME_UP = 175, - - /// - /// Next Track key. - /// - [VirtualKey("Next Track")] - MEDIA_NEXT_TRACK = 176, - - /// - /// Previous Track key. - /// - [VirtualKey("Previous Track")] - MEDIA_PREV_TRACK = 177, - - /// - /// Stop Media key. - /// - [VirtualKey("Stop Media")] - MEDIA_STOP = 178, - - /// - /// Play/Pause Media key. - /// - [VirtualKey("Play/Pause Media")] - MEDIA_PLAY_PAUSE = 179, - - /// - /// Start Mail key. - /// - [VirtualKey("Launch Mail")] - LAUNCH_MAIL = 180, - - /// - /// Select Media key. - /// - [VirtualKey("Launch Media Player")] - LAUNCH_MEDIA_SELECT = 181, - - /// - /// Start Application 1 key. - /// - [VirtualKey("Launch Application 1")] - LAUNCH_APP1 = 182, - - /// - /// Start Application 2 key. - /// - [VirtualKey("Launch Application 2")] - LAUNCH_APP2 = 183, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the ';:' key. - /// - [VirtualKey("Semicolon")] - OEM_1 = 186, - - /// - /// For any country/region, the '+' key. - /// - [VirtualKey("Plus")] - OEM_PLUS = 187, - - /// - /// For any country/region, the ',' key. - /// - [VirtualKey("Comma")] - OEM_COMMA = 188, - - /// - /// For any country/region, the '-' key. - /// - [VirtualKey("Minus")] - OEM_MINUS = 189, - - /// - /// For any country/region, the '.' key. - /// - [VirtualKey("Period")] - OEM_PERIOD = 190, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '/?' key. - /// - [VirtualKey("Forward Slash/Question Mark")] - OEM_2 = 191, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '`~' key. - /// - [VirtualKey("Tilde")] - OEM_3 = 192, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '[{' key. - /// - [VirtualKey("Opening Bracket")] - OEM_4 = 219, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '\|' key. - /// - [VirtualKey("Back Slash/Pipe")] - OEM_5 = 220, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the ']}' key. - /// - [VirtualKey("Closing Bracket")] - OEM_6 = 221, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the 'single-quote/double-quote' (''"') key. - /// - [VirtualKey("Single Quote/Double Quote")] - OEM_7 = 222, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - [VirtualKey("OEM 8")] - OEM_8 = 223, - - /// - /// OEM specific. - /// - - /// - /// 'AX' key on Japanese AX kbd. - /// - [VirtualKey("OEM AX")] - OEM_AX = 225, - - /// - /// Either the angle bracket ("<>") key or the backslash ("\|") key on the RT 102-key keyboard. - /// - [VirtualKey("OEM 102")] - OEM_102 = 226, - - /// - /// OEM specific. - /// - /// - /// Help key on ICO. - /// - [VirtualKey("Help (ICO)")] - ICO_HELP = 227, - - /// - /// OEM specific. - /// - /// - /// 00 key on ICO. - /// - [VirtualKey("00 (ICO)")] - ICO_00 = 228, - - /// - /// IME PROCESS key. - /// - [VirtualKey("IME Process")] - PROCESSKEY = 229, - - /// - /// OEM specific. - /// - /// - /// Clear key on ICO. - /// - [VirtualKey("Clear (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. - /// - [VirtualKey("Packet")] - PACKET = 231, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM Reset")] - OEM_RESET = 233, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM Jump")] - OEM_JUMP = 234, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM PA1")] - OEM_PA1 = 235, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM PA2")] - OEM_PA2 = 236, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM PA3")] - OEM_PA3 = 237, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM WSCTRL")] - OEM_WSCTRL = 238, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM CUSEL")] - OEM_CUSEL = 239, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM ATTN")] - OEM_ATTN = 240, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM Finish")] - OEM_FINISH = 241, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM Copy")] - OEM_COPY = 242, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM Auto")] - OEM_AUTO = 243, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM ENLW")] - OEM_ENLW = 244, - - /// - /// Nokia/Ericsson definition. - /// - [VirtualKey("OEM Backtab")] - OEM_BACKTAB = 245, - - /// - /// Attn key. - /// - [VirtualKey("ATTN")] - ATTN = 246, - - /// - /// CrSel key. - /// - [VirtualKey("CRSEL")] - CRSEL = 247, - - /// - /// ExSel key. - /// - [VirtualKey("EXSEL")] - EXSEL = 248, - - /// - /// Erase EOF key. - /// - [VirtualKey("Erase EOF")] - EREOF = 249, - - /// - /// Play key. - /// - [VirtualKey("Play")] - PLAY = 250, - - /// - /// Zoom key. - /// - [VirtualKey("Zoom")] - ZOOM = 251, - - /// - /// Reserved constant by Windows headers definition. - /// - [VirtualKey("Reserved")] - NONAME = 252, - - /// - /// PA1 key. - /// - [VirtualKey("PA1")] - PA1 = 253, - - /// - /// Clear key. - /// - [VirtualKey("Clear")] - OEM_CLEAR = 254, - } + [VirtualKey("Mouse 3")] + MBUTTON = 4, + + /// + /// X1 mouse button. + /// + + /// + /// NOT contiguous with L and R buttons. + /// + [VirtualKey("Mouse 4")] + XBUTTON1 = 5, + + /// + /// X2 mouse button. + /// + + /// + /// NOT contiguous with L and R buttons. + /// + [VirtualKey("Mouse 5")] + XBUTTON2 = 6, + + /// + /// BACKSPACE key. + /// + [VirtualKey("Backspace")] + BACK = 8, + + /// + /// TAB key. + /// + [VirtualKey("Tab")] + TAB = 9, + + /// + /// CLEAR key. + /// + [VirtualKey("Clear")] + CLEAR = 12, + + /// + /// RETURN key. + /// + [VirtualKey("Return/Enter")] + RETURN = 13, + + /// + /// SHIFT key. + /// + [VirtualKey("Shift")] + SHIFT = 16, + + /// + /// CONTROL key. + /// + [VirtualKey("Control")] + CONTROL = 17, + + /// + /// ALT key. + /// + [VirtualKey("Alt")] + MENU = 18, + + /// + /// PAUSE key. + /// + [VirtualKey("Pause")] + PAUSE = 19, + + /// + /// CAPS LOCK key. + /// + [VirtualKey("Caps Lock")] + CAPITAL = 20, + + /// + /// IME Kana mode. + /// + [VirtualKey("Kana Key")] + KANA = 21, + + /// + /// IME Hangeul mode (maintained for compatibility; use User32.VirtualKey.HANGUL). + /// + [VirtualKey("Hangul Key")] + HANGEUL = KANA, + + /// + /// IME Hangul mode. + /// + [VirtualKey("Hangul Key 2")] + HANGUL = KANA, + + /// + /// IME Junja mode. + /// + [VirtualKey("Junja Key")] + JUNJA = 23, + + /// + /// IME final mode. + /// + [VirtualKey("Final Key")] + FINAL = 24, + + /// + /// IME Hanja mode. + /// + [VirtualKey("Hanja Key")] + HANJA = 25, + + /// + /// IME Kanji mode. + /// + [VirtualKey("Kanji Key")] + KANJI = HANJA, + + /// + /// ESC key. + /// + [VirtualKey("Escape")] + ESCAPE = 27, + + /// + /// IME convert. + /// + [VirtualKey("Convert Key")] + CONVERT = 28, + + /// + /// IME nonconvert. + /// + [VirtualKey("Non-Convert Key")] + NONCONVERT = 29, + + /// + /// IME accept. + /// + [VirtualKey("IME Accept Key")] + ACCEPT = 30, + + /// + /// IME mode change request. + /// + [VirtualKey("IME Mode-Change Key")] + MODECHANGE = 31, + + /// + /// SPACEBAR. + /// + [VirtualKey("Spacebar")] + SPACE = 32, + + /// + /// PAGE UP key. + /// + [VirtualKey("Page Up")] + PRIOR = 33, + + /// + /// PAGE DOWN key. + /// + [VirtualKey("Page Down")] + NEXT = 34, + + /// + /// END key. + /// + [VirtualKey("End")] + END = 35, + + /// + /// HOME key. + /// + [VirtualKey("Home")] + HOME = 36, + + /// + /// LEFT ARROW key. + /// + [VirtualKey("Left Arrow")] + LEFT = 37, + + /// + /// UP ARROW key. + /// + [VirtualKey("Up Arrow")] + UP = 38, + + /// + /// RIGHT ARROW key. + /// + [VirtualKey("Right Arrow")] + RIGHT = 39, + + /// + /// DOWN ARROW key. + /// + [VirtualKey("Down Arrow")] + DOWN = 40, + + /// + /// SELECT key. + /// + [VirtualKey("Select")] + SELECT = 41, + + /// + /// PRINT key. + /// + [VirtualKey("Print")] + PRINT = 42, + + /// + /// EXECUTE key. + /// + [VirtualKey("Execute")] + EXECUTE = 43, + + /// + /// PRINT SCREEN key. + /// + [VirtualKey("Print Screen")] + SNAPSHOT = 44, + + /// + /// INS key. + /// + [VirtualKey("Insert")] + INSERT = 45, + + /// + /// DEL key. + /// + [VirtualKey("Delete")] + DELETE = 46, + + /// + /// HELP key. + /// + [VirtualKey("Help")] + HELP = 47, + + /// + /// 0 key. + /// + [VirtualKey("Number-Row 0")] + KEY_0 = 48, + + /// + /// 1 key. + /// + [VirtualKey("Number-Row 1")] + KEY_1 = 49, + + /// + /// 2 key. + /// + [VirtualKey("Number-Row 2")] + KEY_2 = 50, + + /// + /// 3 key. + /// + [VirtualKey("Number-Row 3")] + KEY_3 = 51, + + /// + /// 4 key. + /// + [VirtualKey("Number-Row 4")] + KEY_4 = 52, + + /// + /// 5 key. + /// + [VirtualKey("Number-Row 5")] + KEY_5 = 53, + + /// + /// 6 key. + /// + [VirtualKey("Number-Row 6")] + KEY_6 = 54, + + /// + /// 7 key. + /// + [VirtualKey("Number-Row 7")] + KEY_7 = 55, + + /// + /// 8 key. + /// + [VirtualKey("Number-Row 8")] + KEY_8 = 56, + + /// + /// 9 key. + /// + [VirtualKey("Number-Row 9")] + KEY_9 = 57, + + /// + /// A key. + /// + [VirtualKey("A")] + A = 65, + + /// + /// B key. + /// + [VirtualKey("B")] + B = 66, + + /// + /// C key. + /// + [VirtualKey("C")] + C = 67, + + /// + /// D key. + /// + [VirtualKey("D")] + D = 68, + + /// + /// E key. + /// + [VirtualKey("E")] + E = 69, + + /// + /// F key. + /// + [VirtualKey("F")] + F = 70, + + /// + /// G key. + /// + [VirtualKey("G")] + G = 71, + + /// + /// H key. + /// + [VirtualKey("H")] + H = 72, + + /// + /// I key. + /// + [VirtualKey("I")] + I = 73, + + /// + /// J key. + /// + [VirtualKey("J")] + J = 74, + + /// + /// K key. + /// + [VirtualKey("K")] + K = 75, + + /// + /// L key. + /// + [VirtualKey("L")] + L = 76, + + /// + /// M key. + /// + [VirtualKey("M")] + M = 77, + + /// + /// N key. + /// + [VirtualKey("N")] + N = 78, + + /// + /// O key. + /// + [VirtualKey("O")] + O = 79, + + /// + /// P key. + /// + [VirtualKey("P")] + P = 80, + + /// + /// Q key. + /// + [VirtualKey("Q")] + Q = 81, + + /// + /// R key. + /// + [VirtualKey("R")] + R = 82, + + /// + /// S key. + /// + [VirtualKey("S")] + S = 83, + + /// + /// T key. + /// + [VirtualKey("T")] + T = 84, + + /// + /// U key. + /// + [VirtualKey("U")] + U = 85, + + /// + /// V key. + /// + [VirtualKey("V")] + V = 86, + + /// + /// W key. + /// + [VirtualKey("W")] + W = 87, + + /// + /// X key. + /// + [VirtualKey("X")] + X = 88, + + /// + /// Y key. + /// + [VirtualKey("Y")] + Y = 89, + + /// + /// Z key. + /// + [VirtualKey("Z")] + Z = 90, + + /// + /// Left Windows key (Natural keyboard). + /// + [VirtualKey("Left Windows")] + LWIN = 91, + + /// + /// Right Windows key (Natural keyboard). + /// + [VirtualKey("Right Windows")] + RWIN = 92, + + /// + /// Applications key (Natural keyboard). + /// + [VirtualKey("Applications")] + APPS = 93, + + /// + /// Computer Sleep key. + /// + [VirtualKey("Sleep")] + SLEEP = 95, + + /// + /// Numeric keypad 0 key. + /// + [VirtualKey("Numpad 0")] + NUMPAD0 = 96, + + /// + /// Numeric keypad 1 key. + /// + [VirtualKey("Numpad 1")] + NUMPAD1 = 97, + + /// + /// Numeric keypad 2 key. + /// + [VirtualKey("Numpad 2")] + NUMPAD2 = 98, + + /// + /// Numeric keypad 3 key. + /// + [VirtualKey("Numpad 3")] + NUMPAD3 = 99, + + /// + /// Numeric keypad 4 key. + /// + [VirtualKey("Numpad 4")] + NUMPAD4 = 100, + + /// + /// Numeric keypad 5 key. + /// + [VirtualKey("Numpad 5")] + NUMPAD5 = 101, + + /// + /// Numeric keypad 6 key. + /// + [VirtualKey("Numpad 6")] + NUMPAD6 = 102, + + /// + /// Numeric keypad 7 key. + /// + [VirtualKey("Numpad 7")] + NUMPAD7 = 103, + + /// + /// Numeric keypad 8 key. + /// + [VirtualKey("Numpad 8")] + NUMPAD8 = 104, + + /// + /// Numeric keypad 9 key. + /// + [VirtualKey("Numpad 9")] + NUMPAD9 = 105, + + /// + /// Multiply key. + /// + [VirtualKey("Numpad Multiply")] + MULTIPLY = 106, + + /// + /// Add key. + /// + [VirtualKey("Numpad Add")] + ADD = 107, + + /// + /// Separator key. + /// + [VirtualKey("Numpad Separator")] + SEPARATOR = 108, + + /// + /// Subtract key. + /// + [VirtualKey("Numpad Subtract")] + SUBTRACT = 109, + + /// + /// Decimal key. + /// + [VirtualKey("Numpad Decimal")] + DECIMAL = 110, + + /// + /// Divide key. + /// + [VirtualKey("Numpad Divide")] + DIVIDE = 111, + + /// + /// F1 Key. + /// + [VirtualKey("F1")] + F1 = 112, + + /// + /// F2 Key. + /// + [VirtualKey("F2")] + F2 = 113, + + /// + /// F3 Key. + /// + [VirtualKey("F3")] + F3 = 114, + + /// + /// F4 Key. + /// + [VirtualKey("F4")] + F4 = 115, + + /// + /// F5 Key. + /// + [VirtualKey("F5")] + F5 = 116, + + /// + /// F6 Key. + /// + [VirtualKey("F6")] + F6 = 117, + + /// + /// F7 Key. + /// + [VirtualKey("F7")] + F7 = 118, + + /// + /// F8 Key. + /// + [VirtualKey("F8")] + F8 = 119, + + /// + /// F9 Key. + /// + [VirtualKey("F9")] + F9 = 120, + + /// + /// F10 Key. + /// + [VirtualKey("F10")] + F10 = 121, + + /// + /// F11 Key. + /// + [VirtualKey("F11")] + F11 = 122, + + /// + /// F12 Key. + /// + [VirtualKey("F12")] + F12 = 123, + + /// + /// F13 Key. + /// + [VirtualKey("F13")] + F13 = 124, + + /// + /// F14 Key. + /// + [VirtualKey("F14")] + F14 = 125, + + /// + /// F15 Key. + /// + [VirtualKey("F15")] + F15 = 126, + + /// + /// F16 Key. + /// + [VirtualKey("F16")] + F16 = 127, + + /// + /// F17 Key. + /// + [VirtualKey("F17")] + F17 = 128, + + /// + /// F18 Key. + /// + [VirtualKey("F18")] + F18 = 129, + + /// + /// F19 Key. + /// + [VirtualKey("F19")] + F19 = 130, + + /// + /// F20 Key. + /// + [VirtualKey("F20")] + F20 = 131, + + /// + /// F21 Key. + /// + [VirtualKey("F21")] + F21 = 132, + + /// + /// F22 Key. + /// + [VirtualKey("F22")] + F22 = 133, + + /// + /// F23 Key. + /// + [VirtualKey("F23")] + F23 = 134, + + /// + /// F24 Key. + /// + [VirtualKey("F24")] + F24 = 135, + + /// + /// NUM LOCK key. + /// + [VirtualKey("Num-Lock")] + NUMLOCK = 144, + + /// + /// SCROLL LOCK key. + /// + [VirtualKey("Scroll-Lock")] + SCROLL = 145, + + /// + /// '=' key on numpad (NEC PC-9800 kbd definitions). + /// + [VirtualKey("Numpad Equals")] + OEM_NEC_EQUAL = 146, + + /// + /// 'Dictionary' key (Fujitsu/OASYS kbd definitions). + /// + [VirtualKey("Dictionary (Fujitsu)")] + OEM_FJ_JISHO = OEM_NEC_EQUAL, + + /// + /// 'Unregister word' key (Fujitsu/OASYS kbd definitions). + /// + [VirtualKey("Unregister word (Fujitsu)")] + OEM_FJ_MASSHOU = 147, + + /// + /// 'Register word' key (Fujitsu/OASYS kbd definitions). + /// + [VirtualKey("Register word (Fujitsu)")] + OEM_FJ_TOUROKU = 148, + + /// + /// 'Left OYAYUBI' key (Fujitsu/OASYS kbd definitions). + /// + [VirtualKey("Left Oyayubi (Fujitsu)")] + OEM_FJ_LOYA = 149, + + /// + /// 'Right OYAYUBI' key (Fujitsu/OASYS kbd definitions). + /// + [VirtualKey("Right Oyayubi (Fujitsu)")] + 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. + /// + [VirtualKey("Left Shift")] + LSHIFT = 160, + + /// + /// Right SHIFT key. + /// + [VirtualKey("Right Shift")] + RSHIFT = 161, + + /// + /// Left CONTROL key. + /// + [VirtualKey("Left Control")] + LCONTROL = 162, + + /// + /// Right CONTROL key. + /// + [VirtualKey("Right Control")] + RCONTROL = 163, + + /// + /// Left MENU key. + /// + [VirtualKey("Left Menu")] + LMENU = 164, + + /// + /// Right MENU key. + /// + [VirtualKey("Right Menu")] + RMENU = 165, + + /// + /// Browser Back key. + /// + [VirtualKey("Browser Back")] + BROWSER_BACK = 166, + + /// + /// Browser Forward key. + /// + [VirtualKey("Browser Forward")] + BROWSER_FORWARD = 167, + + /// + /// Browser Refresh key. + /// + [VirtualKey("Browser Refresh")] + BROWSER_REFRESH = 168, + + /// + /// Browser Stop key. + /// + [VirtualKey("Browser Stop")] + BROWSER_STOP = 169, + + /// + /// Browser Search key. + /// + [VirtualKey("Browser Search")] + BROWSER_SEARCH = 170, + + /// + /// Browser Favorites key. + /// + [VirtualKey("Browser Favorites")] + BROWSER_FAVORITES = 171, + + /// + /// Browser Start and Home key. + /// + [VirtualKey("Browser Home")] + BROWSER_HOME = 172, + + /// + /// Volume Mute key. + /// + [VirtualKey("Mute Volume")] + VOLUME_MUTE = 173, + + /// + /// Volume Down key. + /// + [VirtualKey("Volume Down")] + VOLUME_DOWN = 174, + + /// + /// Volume Up key. + /// + [VirtualKey("Volume Up")] + VOLUME_UP = 175, + + /// + /// Next Track key. + /// + [VirtualKey("Next Track")] + MEDIA_NEXT_TRACK = 176, + + /// + /// Previous Track key. + /// + [VirtualKey("Previous Track")] + MEDIA_PREV_TRACK = 177, + + /// + /// Stop Media key. + /// + [VirtualKey("Stop Media")] + MEDIA_STOP = 178, + + /// + /// Play/Pause Media key. + /// + [VirtualKey("Play/Pause Media")] + MEDIA_PLAY_PAUSE = 179, + + /// + /// Start Mail key. + /// + [VirtualKey("Launch Mail")] + LAUNCH_MAIL = 180, + + /// + /// Select Media key. + /// + [VirtualKey("Launch Media Player")] + LAUNCH_MEDIA_SELECT = 181, + + /// + /// Start Application 1 key. + /// + [VirtualKey("Launch Application 1")] + LAUNCH_APP1 = 182, + + /// + /// Start Application 2 key. + /// + [VirtualKey("Launch Application 2")] + LAUNCH_APP2 = 183, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the ';:' key. + /// + [VirtualKey("Semicolon")] + OEM_1 = 186, + + /// + /// For any country/region, the '+' key. + /// + [VirtualKey("Plus")] + OEM_PLUS = 187, + + /// + /// For any country/region, the ',' key. + /// + [VirtualKey("Comma")] + OEM_COMMA = 188, + + /// + /// For any country/region, the '-' key. + /// + [VirtualKey("Minus")] + OEM_MINUS = 189, + + /// + /// For any country/region, the '.' key. + /// + [VirtualKey("Period")] + OEM_PERIOD = 190, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '/?' key. + /// + [VirtualKey("Forward Slash/Question Mark")] + OEM_2 = 191, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '`~' key. + /// + [VirtualKey("Tilde")] + OEM_3 = 192, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '[{' key. + /// + [VirtualKey("Opening Bracket")] + OEM_4 = 219, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '\|' key. + /// + [VirtualKey("Back Slash/Pipe")] + OEM_5 = 220, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the ']}' key. + /// + [VirtualKey("Closing Bracket")] + OEM_6 = 221, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the 'single-quote/double-quote' (''"') key. + /// + [VirtualKey("Single Quote/Double Quote")] + OEM_7 = 222, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + [VirtualKey("OEM 8")] + OEM_8 = 223, + + /// + /// OEM specific. + /// + + /// + /// 'AX' key on Japanese AX kbd. + /// + [VirtualKey("OEM AX")] + OEM_AX = 225, + + /// + /// Either the angle bracket ("<>") key or the backslash ("\|") key on the RT 102-key keyboard. + /// + [VirtualKey("OEM 102")] + OEM_102 = 226, + + /// + /// OEM specific. + /// + /// + /// Help key on ICO. + /// + [VirtualKey("Help (ICO)")] + ICO_HELP = 227, + + /// + /// OEM specific. + /// + /// + /// 00 key on ICO. + /// + [VirtualKey("00 (ICO)")] + ICO_00 = 228, + + /// + /// IME PROCESS key. + /// + [VirtualKey("IME Process")] + PROCESSKEY = 229, + + /// + /// OEM specific. + /// + /// + /// Clear key on ICO. + /// + [VirtualKey("Clear (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. + /// + [VirtualKey("Packet")] + PACKET = 231, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM Reset")] + OEM_RESET = 233, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM Jump")] + OEM_JUMP = 234, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM PA1")] + OEM_PA1 = 235, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM PA2")] + OEM_PA2 = 236, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM PA3")] + OEM_PA3 = 237, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM WSCTRL")] + OEM_WSCTRL = 238, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM CUSEL")] + OEM_CUSEL = 239, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM ATTN")] + OEM_ATTN = 240, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM Finish")] + OEM_FINISH = 241, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM Copy")] + OEM_COPY = 242, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM Auto")] + OEM_AUTO = 243, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM ENLW")] + OEM_ENLW = 244, + + /// + /// Nokia/Ericsson definition. + /// + [VirtualKey("OEM Backtab")] + OEM_BACKTAB = 245, + + /// + /// Attn key. + /// + [VirtualKey("ATTN")] + ATTN = 246, + + /// + /// CrSel key. + /// + [VirtualKey("CRSEL")] + CRSEL = 247, + + /// + /// ExSel key. + /// + [VirtualKey("EXSEL")] + EXSEL = 248, + + /// + /// Erase EOF key. + /// + [VirtualKey("Erase EOF")] + EREOF = 249, + + /// + /// Play key. + /// + [VirtualKey("Play")] + PLAY = 250, + + /// + /// Zoom key. + /// + [VirtualKey("Zoom")] + ZOOM = 251, + + /// + /// Reserved constant by Windows headers definition. + /// + [VirtualKey("Reserved")] + NONAME = 252, + + /// + /// PA1 key. + /// + [VirtualKey("PA1")] + PA1 = 253, + + /// + /// Clear key. + /// + [VirtualKey("Clear")] + OEM_CLEAR = 254, } diff --git a/Dalamud/Game/ClientState/Keys/VirtualKeyAttribute.cs b/Dalamud/Game/ClientState/Keys/VirtualKeyAttribute.cs index 249491be7..6d0750f0d 100644 --- a/Dalamud/Game/ClientState/Keys/VirtualKeyAttribute.cs +++ b/Dalamud/Game/ClientState/Keys/VirtualKeyAttribute.cs @@ -1,25 +1,24 @@ using System; -namespace Dalamud.Game.ClientState.Keys +namespace Dalamud.Game.ClientState.Keys; + +/// +/// Attribute describing a VirtualKey. +/// +[AttributeUsage(AttributeTargets.Field)] +internal sealed class VirtualKeyAttribute : Attribute { /// - /// Attribute describing a VirtualKey. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Field)] - internal sealed class VirtualKeyAttribute : Attribute + /// Fancy name of this key. + public VirtualKeyAttribute(string fancyName) { - /// - /// Initializes a new instance of the class. - /// - /// Fancy name of this key. - public VirtualKeyAttribute(string fancyName) - { - this.FancyName = fancyName; - } - - /// - /// Gets the fancy name of this virtual key. - /// - public string FancyName { get; init; } + this.FancyName = fancyName; } + + /// + /// Gets the fancy name of this virtual key. + /// + public string FancyName { get; init; } } diff --git a/Dalamud/Game/ClientState/Keys/VirtualKeyExtensions.cs b/Dalamud/Game/ClientState/Keys/VirtualKeyExtensions.cs index 2a94dd5df..22470fe99 100644 --- a/Dalamud/Game/ClientState/Keys/VirtualKeyExtensions.cs +++ b/Dalamud/Game/ClientState/Keys/VirtualKeyExtensions.cs @@ -1,20 +1,19 @@ using Dalamud.Utility; -namespace Dalamud.Game.ClientState.Keys +namespace Dalamud.Game.ClientState.Keys; + +/// +/// Extension methods for . +/// +public static class VirtualKeyExtensions { /// - /// Extension methods for . + /// Get the fancy name associated with this key. /// - public static class VirtualKeyExtensions + /// The they key to act on. + /// The key's fancy name. + public static string GetFancyName(this VirtualKey key) { - /// - /// Get the fancy name associated with this key. - /// - /// The they key to act on. - /// The key's fancy name. - public static string GetFancyName(this VirtualKey key) - { - return key.GetAttribute().FancyName; - } + return key.GetAttribute().FancyName; } } 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 71c439f64..babb9f5d6 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -9,138 +9,137 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed partial class ObjectTable : IServiceType { - /// - /// This collection represents the currently spawned FFXIV game objects. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class ObjectTable : IServiceType + private const int ObjectTableLength = 596; + + private readonly ClientStateAddressResolver address; + + [ServiceManager.ServiceConstructor] + private ObjectTable(ClientState clientState) { - private const int ObjectTableLength = 596; + this.address = clientState.AddressResolver; - private readonly ClientStateAddressResolver address; + Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); + } - [ServiceManager.ServiceConstructor] - private ObjectTable(ClientState clientState) + /// + /// 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 = clientState.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.GetNullable(); - - if (clientState == null || 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.GetNullable(); + + if (clientState == null || 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 0c97c8bcf..ccd89e6a3 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -4,165 +4,164 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed unsafe class TargetManager : IServiceType { - /// - /// Get and set various kinds of targets for the player. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe class TargetManager : IServiceType + [ServiceManager.ServiceDependency] + private readonly ClientState clientState = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly ObjectTable objectTable = Service.Get(); + + private readonly ClientStateAddressResolver address; + + [ServiceManager.ServiceConstructor] + private TargetManager() { - [ServiceManager.ServiceDependency] - private readonly ClientState clientState = Service.Get(); - - [ServiceManager.ServiceDependency] - private readonly ObjectTable objectTable = Service.Get(); - - private readonly ClientStateAddressResolver address; - - [ServiceManager.ServiceConstructor] - private TargetManager() - { - this.address = this.clientState.AddressResolver; - } - - /// - /// Gets the address of the target manager. - /// - public IntPtr Address => this.address.TargetManager; - - /// - /// Gets or sets the current target. - /// - public GameObject? Target - { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target); - set => this.SetTarget(value); - } - - /// - /// Gets or sets the mouseover target. - /// - public GameObject? MouseOverTarget - { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget); - set => this.SetMouseOverTarget(value); - } - - /// - /// Gets or sets the focus target. - /// - public GameObject? FocusTarget - { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget); - set => this.SetFocusTarget(value); - } - - /// - /// Gets or sets the previous target. - /// - public GameObject? PreviousTarget - { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget); - set => this.SetPreviousTarget(value); - } - - /// - /// Gets or sets the soft target. - /// - public GameObject? SoftTarget - { - get => this.objectTable.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 = this.clientState.AddressResolver; } + + /// + /// Gets the address of the target manager. + /// + public IntPtr Address => this.address.TargetManager; + + /// + /// Gets or sets the current target. + /// + public GameObject? Target + { + get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target); + set => this.SetTarget(value); + } + + /// + /// Gets or sets the mouseover target. + /// + public GameObject? MouseOverTarget + { + get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget); + set => this.SetMouseOverTarget(value); + } + + /// + /// Gets or sets the focus target. + /// + public GameObject? FocusTarget + { + get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget); + set => this.SetFocusTarget(value); + } + + /// + /// Gets or sets the previous target. + /// + public GameObject? PreviousTarget + { + get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget); + set => this.SetPreviousTarget(value); + } + + /// + /// Gets or sets the soft target. + /// + public GameObject? SoftTarget + { + get => this.objectTable.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 a3f9b9f89..651f97834 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. - /// - protected internal 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. + /// + protected internal 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 666223429..093da7ebd 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -6,107 +6,106 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; using Lumina.Excel.GeneratedSheets; -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 current online status of the character. - /// - public ExcelResolver OnlineStatus => new(this.Struct->OnlineStatus); - - /// - /// Gets the status flags. - /// - public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags; - - /// - /// Gets the underlying structure. - /// - protected internal 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 current online status of the character. + /// + public ExcelResolver OnlineStatus => new(this.Struct->OnlineStatus); + + /// + /// Gets the status flags. + /// + public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags; + + /// + /// Gets the underlying structure. + /// + protected internal 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 55627e0c3..9cd0cccdb 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.GetNullable(); - - if (actor is null || clientState == 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. - /// - protected internal 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.GetNullable(); + + if (actor is null || clientState == 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. + /// + protected internal 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 40bba06d4..5312be67c 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -7,180 +7,179 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed unsafe partial class PartyList : IServiceType { - /// - /// This collection represents the actors present in your party or alliance. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe partial class PartyList : IServiceType + private const int GroupLength = 8; + private const int AllianceLength = 20; + + [ServiceManager.ServiceDependency] + private readonly ClientState clientState = Service.Get(); + + private readonly ClientStateAddressResolver address; + + [ServiceManager.ServiceConstructor] + private PartyList() { - private const int GroupLength = 8; - private const int AllianceLength = 20; + this.address = this.clientState.AddressResolver; - [ServiceManager.ServiceDependency] - private readonly ClientState clientState = Service.Get(); - - private readonly ClientStateAddressResolver address; - - [ServiceManager.ServiceConstructor] - private PartyList() - { - this.address = this.clientState.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; - - /// - /// Gets the ID of the party. - /// - public long PartyId => this.GroupManagerStruct->PartyId; - - 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) - { - if (this.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) - { - if (this.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; + + /// + /// Gets the ID of the party. + /// + public long PartyId => this.GroupManagerStruct->PartyId; + + 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) + { + if (this.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) + { + if (this.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 64e6fda64..ef65e3b19 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -8,105 +8,104 @@ using Dalamud.Game.ClientState.Statuses; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -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 fa9ce0ec2..722c5a6bc 100644 --- a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs +++ b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs @@ -1,38 +1,37 @@ 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); - - /// - /// Gets GameData linked to this excel row with the specified language. - /// - /// The language. - /// The ExcelRow in the specified language. - public T? GetWithLanguage(ClientLanguage language) => Service.Get().GetExcelSheet(language)?.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); + + /// + /// Gets GameData linked to this excel row with the specified language. + /// + /// The language. + /// The ExcelRow in the specified language. + public T? GetWithLanguage(ClientLanguage language) => Service.Get().GetExcelSheet(language)?.GetRow(this.Id); } diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index 883fba43b..aa5759a03 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 ushort 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 ushort 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 7d46b0842..7bb429063 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -10,173 +10,172 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class CommandManager : IServiceType, IDisposable { - /// - /// This class manages registered in-game slash commands. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class CommandManager : IServiceType, IDisposable + 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; + + [ServiceManager.ServiceDependency] + private readonly ChatGui chatGui = Service.Get(); + + [ServiceManager.ServiceConstructor] + private CommandManager(DalamudStartInfo startInfo) { - 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; - - [ServiceManager.ServiceDependency] - private readonly ChatGui chatGui = Service.Get(); - - [ServiceManager.ServiceConstructor] - private CommandManager(DalamudStartInfo startInfo) + this.currentLangCommandRegex = startInfo.Language switch { - this.currentLangCommandRegex = startInfo.Language switch - { - ClientLanguage.Japanese => this.commandRegexJp, - ClientLanguage.English => this.commandRegexEn, - ClientLanguage.German => this.commandRegexDe, - ClientLanguage.French => this.commandRegexFr, - _ => this.currentLangCommandRegex, - }; + ClientLanguage.Japanese => this.commandRegexJp, + ClientLanguage.English => this.commandRegexEn, + ClientLanguage.German => this.commandRegexDe, + ClientLanguage.French => this.commandRegexFr, + _ => this.currentLangCommandRegex, + }; - this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled; - } + this.chatGui.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) + /// + /// 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; + + 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); + } + + /// + void IDisposable.Dispose() + { + this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled; + } + + 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); - } - - /// - void IDisposable.Dispose() - { - this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled; - } - - 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 cb1f4e2a9..2de448aae 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -15,540 +15,539 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class Framework : IDisposable, IServiceType { - /// - /// This class represents the Framework of the native game client and grants access to various subsystems. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class Framework : IDisposable, IServiceType + private static Stopwatch statsStopwatch = new(); + + private readonly Stopwatch updateStopwatch = new(); + + private readonly Hook updateHook; + private readonly Hook destroyHook; + + private readonly object runOnNextTickTaskListSync = new(); + private List runOnNextTickTaskList = new(); + private List runOnNextTickTaskList2 = new(); + + private Thread? frameworkUpdateThread; + + [ServiceManager.ServiceConstructor] + private Framework(SigScanner sigScanner) { - private static Stopwatch statsStopwatch = new(); + this.Address = new FrameworkAddressResolver(); + this.Address.Setup(sigScanner); - private readonly Stopwatch updateStopwatch = new(); + this.updateHook = Hook.FromAddress(this.Address.TickAddress, this.HandleFrameworkUpdate); + this.destroyHook = Hook.FromAddress(this.Address.DestroyAddress, this.HandleFrameworkDestroy); + } - private readonly Hook updateHook; - private readonly Hook destroyHook; + /// + /// A delegate type used with the event. + /// + /// The Framework instance. + public delegate void OnUpdateDelegate(Framework framework); - private readonly object runOnNextTickTaskListSync = new(); - private List runOnNextTickTaskList = new(); - private List runOnNextTickTaskList2 = new(); + /// + /// 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); - private Thread? frameworkUpdateThread; + /// + /// A delegate type used during the native Framework::free. + /// + /// The native Framework address. + public delegate IntPtr OnDestroyDelegate(); - [ServiceManager.ServiceConstructor] - private Framework(SigScanner sigScanner) + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate bool OnUpdateDetour(IntPtr framework); + + private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate + + /// + /// Event that gets fired every time the game framework updates. + /// + public event OnUpdateDelegate Update; + + /// + /// Gets or sets a value indicating whether the collection of stats is enabled. + /// + public static bool StatsEnabled { get; set; } + + /// + /// Gets the stats history mapping. + /// + public static Dictionary> StatsHistory { get; } = new(); + + /// + /// Gets a raw pointer to the instance of Client::Framework. + /// + public FrameworkAddressResolver Address { get; } + + /// + /// Gets the last time that the Framework Update event was triggered. + /// + public DateTime LastUpdate { get; private set; } = DateTime.MinValue; + + /// + /// Gets the last time in UTC that the Framework Update event was triggered. + /// + public DateTime LastUpdateUTC { 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 a value indicating whether currently executing code is running in the game's framework update thread. + /// + public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread; + + /// + /// Gets a value indicating whether game Framework is unloading. + /// + public bool IsFrameworkUnloading { get; internal set; } + + /// + /// Gets or sets a value indicating whether to dispatch update events. + /// + internal bool DispatchUpdateEvents { get; set; } = true; + + /// + /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. + /// + /// Return type. + /// Function to call. + /// Task representing the pending or already completed function. + public Task RunOnFrameworkThread(Func func) => + this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? Task.FromResult(func()) : this.RunOnTick(func); + + /// + /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. + /// + /// Function to call. + /// Task representing the pending or already completed function. + public Task RunOnFrameworkThread(Action action) + { + if (this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading) { - this.Address = new FrameworkAddressResolver(); - this.Address.Setup(sigScanner); + try + { + action(); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + else + { + return this.RunOnTick(action); + } + } - this.updateHook = Hook.FromAddress(this.Address.TickAddress, this.HandleFrameworkUpdate); - this.destroyHook = Hook.FromAddress(this.Address.DestroyAddress, this.HandleFrameworkDestroy); + /// + /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. + /// + /// Return type. + /// Function to call. + /// Task representing the pending or already completed function. + public Task RunOnFrameworkThread(Func> func) => + this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func); + + /// + /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. + /// + /// Function to call. + /// Task representing the pending or already completed function. + public Task RunOnFrameworkThread(Func func) => + this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func); + + /// + /// Run given function in upcoming Framework.Tick call. + /// + /// Return type. + /// Function to call. + /// Wait for given timespan before calling this function. + /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. + /// Cancellation token which will prevent the execution of this function if wait conditions are not met. + /// Task representing the pending function. + public Task RunOnTick(Func func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) + { + if (this.IsFrameworkUnloading) + { + if (delay == default && delayTicks == default) + return this.RunOnFrameworkThread(func); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + return Task.FromCanceled(cts.Token); } - /// - /// A delegate type used with the event. - /// - /// The Framework instance. - public delegate void OnUpdateDelegate(Framework framework); - - /// - /// 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 during the native Framework::free. - /// - /// The native Framework address. - public delegate IntPtr OnDestroyDelegate(); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate bool OnUpdateDetour(IntPtr framework); - - private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate - - /// - /// Event that gets fired every time the game framework updates. - /// - public event OnUpdateDelegate Update; - - /// - /// Gets or sets a value indicating whether the collection of stats is enabled. - /// - public static bool StatsEnabled { get; set; } - - /// - /// Gets the stats history mapping. - /// - public static Dictionary> StatsHistory { get; } = new(); - - /// - /// Gets a raw pointer to the instance of Client::Framework. - /// - public FrameworkAddressResolver Address { get; } - - /// - /// Gets the last time that the Framework Update event was triggered. - /// - public DateTime LastUpdate { get; private set; } = DateTime.MinValue; - - /// - /// Gets the last time in UTC that the Framework Update event was triggered. - /// - public DateTime LastUpdateUTC { 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 a value indicating whether currently executing code is running in the game's framework update thread. - /// - public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread; - - /// - /// Gets a value indicating whether game Framework is unloading. - /// - public bool IsFrameworkUnloading { get; internal set; } - - /// - /// Gets or sets a value indicating whether to dispatch update events. - /// - internal bool DispatchUpdateEvents { get; set; } = true; - - /// - /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. - /// - /// Return type. - /// Function to call. - /// Task representing the pending or already completed function. - public Task RunOnFrameworkThread(Func func) => - this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? Task.FromResult(func()) : this.RunOnTick(func); - - /// - /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. - /// - /// Function to call. - /// Task representing the pending or already completed function. - public Task RunOnFrameworkThread(Action action) + var tcs = new TaskCompletionSource(); + lock (this.runOnNextTickTaskListSync) { - if (this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading) + this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc() { - try + RemainingTicks = delayTicks, + RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), + CancellationToken = cancellationToken, + TaskCompletionSource = tcs, + Func = func, + }); + } + + return tcs.Task; + } + + /// + /// Run given function in upcoming Framework.Tick call. + /// + /// Function to call. + /// Wait for given timespan before calling this function. + /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. + /// Cancellation token which will prevent the execution of this function if wait conditions are not met. + /// Task representing the pending function. + public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) + { + if (this.IsFrameworkUnloading) + { + if (delay == default && delayTicks == default) + return this.RunOnFrameworkThread(action); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + return Task.FromCanceled(cts.Token); + } + + var tcs = new TaskCompletionSource(); + lock (this.runOnNextTickTaskListSync) + { + this.runOnNextTickTaskList.Add(new RunOnNextTickTaskAction() + { + RemainingTicks = delayTicks, + RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), + CancellationToken = cancellationToken, + TaskCompletionSource = tcs, + Action = action, + }); + } + + return tcs.Task; + } + + /// + /// Run given function in upcoming Framework.Tick call. + /// + /// Return type. + /// Function to call. + /// Wait for given timespan before calling this function. + /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. + /// Cancellation token which will prevent the execution of this function if wait conditions are not met. + /// Task representing the pending function. + public Task RunOnTick(Func> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) + { + if (this.IsFrameworkUnloading) + { + if (delay == default && delayTicks == default) + return this.RunOnFrameworkThread(func); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + return Task.FromCanceled(cts.Token); + } + + var tcs = new TaskCompletionSource>(); + lock (this.runOnNextTickTaskListSync) + { + this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc>() + { + RemainingTicks = delayTicks, + RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), + CancellationToken = cancellationToken, + TaskCompletionSource = tcs, + Func = func, + }); + } + + return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap(); + } + + /// + /// Run given function in upcoming Framework.Tick call. + /// + /// Function to call. + /// Wait for given timespan before calling this function. + /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. + /// Cancellation token which will prevent the execution of this function if wait conditions are not met. + /// Task representing the pending function. + public Task RunOnTick(Func func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) + { + if (this.IsFrameworkUnloading) + { + if (delay == default && delayTicks == default) + return this.RunOnFrameworkThread(func); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + return Task.FromCanceled(cts.Token); + } + + var tcs = new TaskCompletionSource(); + lock (this.runOnNextTickTaskListSync) + { + this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc() + { + RemainingTicks = delayTicks, + RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), + CancellationToken = cancellationToken, + TaskCompletionSource = tcs, + Func = func, + }); + } + + return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + void IDisposable.Dispose() + { + this.RunOnFrameworkThread(() => + { + // ReSharper disable once AccessToDisposedClosure + this.updateHook.Disable(); + + // ReSharper disable once AccessToDisposedClosure + this.destroyHook.Disable(); + }).Wait(); + + this.updateHook.Dispose(); + this.destroyHook.Dispose(); + + this.updateStopwatch.Reset(); + statsStopwatch.Reset(); + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.updateHook.Enable(); + this.destroyHook.Enable(); + } + + private void RunPendingTickTasks() + { + if (this.runOnNextTickTaskList.Count == 0 && this.runOnNextTickTaskList2.Count == 0) + return; + + for (var i = 0; i < 2; i++) + { + lock (this.runOnNextTickTaskListSync) + (this.runOnNextTickTaskList, this.runOnNextTickTaskList2) = (this.runOnNextTickTaskList2, this.runOnNextTickTaskList); + + this.runOnNextTickTaskList2.RemoveAll(x => x.Run()); + } + } + + private bool HandleFrameworkUpdate(IntPtr framework) + { + this.frameworkUpdateThread ??= Thread.CurrentThread; + + ThreadSafety.MarkMainThread(); + + try + { + var chatGui = Service.GetNullable(); + var toastGui = Service.GetNullable(); + var gameNetwork = Service.GetNullable(); + if (chatGui == null || toastGui == null || gameNetwork == null) + goto original; + + chatGui.UpdateQueue(); + toastGui.UpdateQueue(); + gameNetwork.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; + + this.RunPendingTickTasks(); + + 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) { - action(); - return Task.CompletedTask; + statsStopwatch.Restart(); + try + { + d.Method.Invoke(d.Target, new object[] { this }); + } + catch (Exception ex) + { + Log.Error(ex, "Exception while dispatching Framework::Update event."); + } + + 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); + } } - catch (Exception ex) + + // Cleanup handlers that are no longer being called + foreach (var key in notUpdated) { - return Task.FromException(ex); + if (StatsHistory[key].Count > 0) + { + StatsHistory[key].RemoveAt(0); + } + else + { + StatsHistory.Remove(key); + } } } else { - return this.RunOnTick(action); + this.Update?.InvokeSafely(this); } } - /// - /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. - /// - /// Return type. - /// Function to call. - /// Task representing the pending or already completed function. - public Task RunOnFrameworkThread(Func> func) => - this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func); - - /// - /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. - /// - /// Function to call. - /// Task representing the pending or already completed function. - public Task RunOnFrameworkThread(Func func) => - this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func); - - /// - /// Run given function in upcoming Framework.Tick call. - /// - /// Return type. - /// Function to call. - /// Wait for given timespan before calling this function. - /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. - /// Cancellation token which will prevent the execution of this function if wait conditions are not met. - /// Task representing the pending function. - public Task RunOnTick(Func func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) - { - if (this.IsFrameworkUnloading) - { - if (delay == default && delayTicks == default) - return this.RunOnFrameworkThread(func); - - var cts = new CancellationTokenSource(); - cts.Cancel(); - return Task.FromCanceled(cts.Token); - } - - var tcs = new TaskCompletionSource(); - lock (this.runOnNextTickTaskListSync) - { - this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc() - { - RemainingTicks = delayTicks, - RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), - CancellationToken = cancellationToken, - TaskCompletionSource = tcs, - Func = func, - }); - } - - return tcs.Task; - } - - /// - /// Run given function in upcoming Framework.Tick call. - /// - /// Function to call. - /// Wait for given timespan before calling this function. - /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. - /// Cancellation token which will prevent the execution of this function if wait conditions are not met. - /// Task representing the pending function. - public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) - { - if (this.IsFrameworkUnloading) - { - if (delay == default && delayTicks == default) - return this.RunOnFrameworkThread(action); - - var cts = new CancellationTokenSource(); - cts.Cancel(); - return Task.FromCanceled(cts.Token); - } - - var tcs = new TaskCompletionSource(); - lock (this.runOnNextTickTaskListSync) - { - this.runOnNextTickTaskList.Add(new RunOnNextTickTaskAction() - { - RemainingTicks = delayTicks, - RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), - CancellationToken = cancellationToken, - TaskCompletionSource = tcs, - Action = action, - }); - } - - return tcs.Task; - } - - /// - /// Run given function in upcoming Framework.Tick call. - /// - /// Return type. - /// Function to call. - /// Wait for given timespan before calling this function. - /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. - /// Cancellation token which will prevent the execution of this function if wait conditions are not met. - /// Task representing the pending function. - public Task RunOnTick(Func> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) - { - if (this.IsFrameworkUnloading) - { - if (delay == default && delayTicks == default) - return this.RunOnFrameworkThread(func); - - var cts = new CancellationTokenSource(); - cts.Cancel(); - return Task.FromCanceled(cts.Token); - } - - var tcs = new TaskCompletionSource>(); - lock (this.runOnNextTickTaskListSync) - { - this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc>() - { - RemainingTicks = delayTicks, - RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), - CancellationToken = cancellationToken, - TaskCompletionSource = tcs, - Func = func, - }); - } - - return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap(); - } - - /// - /// Run given function in upcoming Framework.Tick call. - /// - /// Function to call. - /// Wait for given timespan before calling this function. - /// Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter. - /// Cancellation token which will prevent the execution of this function if wait conditions are not met. - /// Task representing the pending function. - public Task RunOnTick(Func func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default) - { - if (this.IsFrameworkUnloading) - { - if (delay == default && delayTicks == default) - return this.RunOnFrameworkThread(func); - - var cts = new CancellationTokenSource(); - cts.Cancel(); - return Task.FromCanceled(cts.Token); - } - - var tcs = new TaskCompletionSource(); - lock (this.runOnNextTickTaskListSync) - { - this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc() - { - RemainingTicks = delayTicks, - RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds), - CancellationToken = cancellationToken, - TaskCompletionSource = tcs, - Func = func, - }); - } - - return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - void IDisposable.Dispose() - { - this.RunOnFrameworkThread(() => - { - // ReSharper disable once AccessToDisposedClosure - this.updateHook.Disable(); - - // ReSharper disable once AccessToDisposedClosure - this.destroyHook.Disable(); - }).Wait(); - - this.updateHook.Dispose(); - this.destroyHook.Dispose(); - - this.updateStopwatch.Reset(); - statsStopwatch.Reset(); - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction() - { - this.updateHook.Enable(); - this.destroyHook.Enable(); - } - - private void RunPendingTickTasks() - { - if (this.runOnNextTickTaskList.Count == 0 && this.runOnNextTickTaskList2.Count == 0) - return; - - for (var i = 0; i < 2; i++) - { - lock (this.runOnNextTickTaskListSync) - (this.runOnNextTickTaskList, this.runOnNextTickTaskList2) = (this.runOnNextTickTaskList2, this.runOnNextTickTaskList); - - this.runOnNextTickTaskList2.RemoveAll(x => x.Run()); - } - } - - private bool HandleFrameworkUpdate(IntPtr framework) - { - this.frameworkUpdateThread ??= Thread.CurrentThread; - - ThreadSafety.MarkMainThread(); - - try - { - var chatGui = Service.GetNullable(); - var toastGui = Service.GetNullable(); - var gameNetwork = Service.GetNullable(); - if (chatGui == null || toastGui == null || gameNetwork == null) - goto original; - - chatGui.UpdateQueue(); - toastGui.UpdateQueue(); - gameNetwork.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; - - this.RunPendingTickTasks(); - - 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(); - try - { - d.Method.Invoke(d.Target, new object[] { this }); - } - catch (Exception ex) - { - Log.Error(ex, "Exception while dispatching Framework::Update event."); - } - - 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?.InvokeSafely(this); - } - } - original: - return this.updateHook.OriginalDisposeSafe(framework); - } + return this.updateHook.OriginalDisposeSafe(framework); + } - private bool HandleFrameworkDestroy(IntPtr framework) + private bool HandleFrameworkDestroy(IntPtr framework) + { + this.IsFrameworkUnloading = true; + this.DispatchUpdateEvents = false; + + Log.Information("Framework::Destroy!"); + Service.Get().Unload(); + this.RunPendingTickTasks(); + ServiceManager.UnloadAllServices(); + Log.Information("Framework::Destroy OK!"); + + return this.destroyHook.OriginalDisposeSafe(framework); + } + + private abstract class RunOnNextTickTaskBase + { + internal int RemainingTicks { get; set; } + + internal long RunAfterTickCount { get; init; } + + internal CancellationToken CancellationToken { get; init; } + + internal bool Run() { - this.IsFrameworkUnloading = true; - this.DispatchUpdateEvents = false; - - Log.Information("Framework::Destroy!"); - Service.Get().Unload(); - this.RunPendingTickTasks(); - ServiceManager.UnloadAllServices(); - Log.Information("Framework::Destroy OK!"); - - return this.destroyHook.OriginalDisposeSafe(framework); - } - - private abstract class RunOnNextTickTaskBase - { - internal int RemainingTicks { get; set; } - - internal long RunAfterTickCount { get; init; } - - internal CancellationToken CancellationToken { get; init; } - - internal bool Run() + if (this.CancellationToken.IsCancellationRequested) { - if (this.CancellationToken.IsCancellationRequested) - { - this.CancelImpl(); - return true; - } - - if (this.RemainingTicks > 0) - this.RemainingTicks -= 1; - if (this.RemainingTicks > 0) - return false; - - if (this.RunAfterTickCount > Environment.TickCount64) - return false; - - this.RunImpl(); - + this.CancelImpl(); return true; } - protected abstract void RunImpl(); + if (this.RemainingTicks > 0) + this.RemainingTicks -= 1; + if (this.RemainingTicks > 0) + return false; - protected abstract void CancelImpl(); + if (this.RunAfterTickCount > Environment.TickCount64) + return false; + + this.RunImpl(); + + return true; } - private class RunOnNextTickTaskFunc : RunOnNextTickTaskBase + protected abstract void RunImpl(); + + protected abstract void CancelImpl(); + } + + private class RunOnNextTickTaskFunc : RunOnNextTickTaskBase + { + internal TaskCompletionSource TaskCompletionSource { get; init; } + + internal Func Func { get; init; } + + protected override void RunImpl() { - internal TaskCompletionSource TaskCompletionSource { get; init; } - - internal Func Func { get; init; } - - protected override void RunImpl() + try { - try - { - this.TaskCompletionSource.SetResult(this.Func()); - } - catch (Exception ex) - { - this.TaskCompletionSource.SetException(ex); - } + this.TaskCompletionSource.SetResult(this.Func()); } - - protected override void CancelImpl() + catch (Exception ex) { - this.TaskCompletionSource.SetCanceled(); + this.TaskCompletionSource.SetException(ex); } } - private class RunOnNextTickTaskAction : RunOnNextTickTaskBase + protected override void CancelImpl() { - internal TaskCompletionSource TaskCompletionSource { get; init; } + this.TaskCompletionSource.SetCanceled(); + } + } - internal Action Action { get; init; } + private class RunOnNextTickTaskAction : RunOnNextTickTaskBase + { + internal TaskCompletionSource TaskCompletionSource { get; init; } - protected override void RunImpl() + internal Action Action { get; init; } + + protected override void RunImpl() + { + try { - try - { - this.Action(); - this.TaskCompletionSource.SetResult(); - } - catch (Exception ex) - { - this.TaskCompletionSource.SetException(ex); - } + this.Action(); + this.TaskCompletionSource.SetResult(); } - - protected override void CancelImpl() + catch (Exception ex) { - this.TaskCompletionSource.SetCanceled(); + this.TaskCompletionSource.SetException(ex); } } + + protected override void CancelImpl() + { + this.TaskCompletionSource.SetCanceled(); + } } } diff --git a/Dalamud/Game/FrameworkAddressResolver.cs b/Dalamud/Game/FrameworkAddressResolver.cs index 2c6a860e0..e3d128f0f 100644 --- a/Dalamud/Game/FrameworkAddressResolver.cs +++ b/Dalamud/Game/FrameworkAddressResolver.cs @@ -1,49 +1,48 @@ using System; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// The address resolver for the class. +/// +public sealed unsafe class FrameworkAddressResolver : BaseAddressResolver { /// - /// The address resolver for the class. + /// Gets the base address of the Framework object. /// - public sealed unsafe class FrameworkAddressResolver : BaseAddressResolver + [Obsolete("Please use FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance() instead.")] + public IntPtr BaseAddress => new(FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()); + + /// + /// Gets the address for the function that is called once the Framework is destroyed. + /// + public IntPtr DestroyAddress { get; private set; } + + /// + /// Gets the address for the function that is called once the Framework is free'd. + /// + public IntPtr FreeAddress { get; private set; } + + /// + /// Gets the function that is called every tick. + /// + public IntPtr TickAddress { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the base address of the Framework object. - /// - [Obsolete("Please use FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance() instead.")] - public IntPtr BaseAddress => new(FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()); + this.SetupFramework(sig); + } - /// - /// Gets the address for the function that is called once the Framework is destroyed. - /// - public IntPtr DestroyAddress { get; private set; } + private void SetupFramework(SigScanner scanner) + { + this.DestroyAddress = + scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF"); - /// - /// Gets the address for the function that is called once the Framework is free'd. - /// - public IntPtr FreeAddress { get; private set; } + this.FreeAddress = + scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ??"); - /// - /// Gets the function that is called every tick. - /// - public IntPtr TickAddress { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) - { - this.SetupFramework(sig); - } - - private void SetupFramework(SigScanner scanner) - { - this.DestroyAddress = - scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF"); - - this.FreeAddress = - scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ??"); - - this.TickAddress = - scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??"); - } + this.TickAddress = + scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??"); } } diff --git a/Dalamud/Game/GameVersion.cs b/Dalamud/Game/GameVersion.cs index a93e0bff2..2b2021e60 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 48ff123c6..93185caf9 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -14,446 +14,445 @@ using Dalamud.IoC.Internal; using Dalamud.Utility; using Serilog; -namespace Dalamud.Game.Gui +namespace Dalamud.Game.Gui; + +/// +/// This class handles interacting with the native chat UI. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class ChatGui : IDisposable, IServiceType { - /// - /// This class handles interacting with the native chat UI. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class ChatGui : IDisposable, IServiceType + 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; + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration configuration = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly LibcFunction libcFunction = Service.Get(); + + private IntPtr baseAddress = IntPtr.Zero; + + [ServiceManager.ServiceConstructor] + private ChatGui(SigScanner sigScanner) { - private readonly ChatGuiAddressResolver address; + this.address = new ChatGuiAddressResolver(); + this.address.Setup(sigScanner); - private readonly Queue chatQueue = new(); - private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); + this.printMessageHook = Hook.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour); + this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); + this.interactableLinkClickedHook = Hook.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); + } - private readonly Hook printMessageHook; - private readonly Hook populateItemLinkHook; - private readonly Hook interactableLinkClickedHook; + /// + /// 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); - [ServiceManager.ServiceDependency] - private readonly DalamudConfiguration configuration = Service.Get(); + /// + /// 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); - [ServiceManager.ServiceDependency] - private readonly LibcFunction libcFunction = Service.Get(); + /// + /// 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); - 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. + public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); - [ServiceManager.ServiceConstructor] - private ChatGui(SigScanner sigScanner) + [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; } + + /// + /// Dispose of managed and unmanaged resources. + /// + void IDisposable.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) + { + // Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message); + this.PrintChat(new XivChatEntry { - this.address = new ChatGuiAddressResolver(); - this.address.Setup(sigScanner); + Message = message, + Type = this.configuration.GeneralChatType, + }); + } - this.printMessageHook = Hook.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour); - this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); - this.interactableLinkClickedHook = Hook.FromAddress(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; } - - /// - /// Dispose of managed and unmanaged resources. - /// - void IDisposable.Dispose() + /// + /// 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) + { + // Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue); + this.PrintChat(new XivChatEntry { - this.printMessageHook.Dispose(); - this.populateItemLinkHook.Dispose(); - this.interactableLinkClickedHook.Dispose(); - } + Message = message, + Type = this.configuration.GeneralChatType, + }); + } - /// - /// 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(string message) + { + // Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message); + 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) + /// + /// 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 { - // Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message); - 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) { - Message = message, - Type = this.configuration.GeneralChatType, - }); + continue; + } + + var senderRaw = (chat.Name ?? string.Empty).Encode(); + using var senderOwned = this.libcFunction.NewString(senderRaw); + + var messageRaw = (chat.Message ?? string.Empty).Encode(); + using var messageOwned = this.libcFunction.NewString(messageRaw); + + this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters); } + } - /// - /// 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) + /// + /// 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)) { - // Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = this.configuration.GeneralChatType, - }); + this.dalamudLinkHandlers.Remove(handler); } + } - /// - /// 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) + /// + /// 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))) { - // Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = XivChatType.Urgent, - }); + this.dalamudLinkHandlers.Remove((pluginName, commandId)); } + } - /// - /// 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) + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction) + { + this.printMessageHook.Enable(); + this.populateItemLinkHook.Enable(); + this.interactableLinkClickedHook.Enable(); + } + + private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) + { + try { - // Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = XivChatType.Urgent, - }); + 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}"); } - - /// - /// Process a chat queue. - /// - public void UpdateQueue() + catch (Exception ex) { - while (this.chatQueue.Count > 0) - { - var chat = this.chatQueue.Dequeue(); + Log.Error(ex, "Exception onPopulateItemLink hook."); + this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + } + } - if (this.baseAddress == IntPtr.Zero) + 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; + + var invocationList = this.CheckMessageHandled.GetInvocationList(); + foreach (var @delegate in invocationList) + { + try { - continue; + var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate; + messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); + } + catch (Exception e) + { + Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name); } - - var senderRaw = (chat.Name ?? string.Empty).Encode(); - using var senderOwned = this.libcFunction.NewString(senderRaw); - - var messageRaw = (chat.Message ?? string.Empty).Encode(); - using var messageOwned = this.libcFunction.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)) + if (!isHandled) { - 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)); - } - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction) - { - this.printMessageHook.Enable(); - this.populateItemLinkHook.Enable(); - this.interactableLinkClickedHook.Enable(); - } - - 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; - - var invocationList = this.CheckMessageHandled.GetInvocationList(); + invocationList = this.ChatMessage.GetInvocationList(); foreach (var @delegate in invocationList) { try { - var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate; + var messageHandledDelegate = @delegate as OnMessageDelegate; messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); } catch (Exception e) { - Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name); + Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name); } } + } - if (!isHandled) - { - invocationList = this.ChatMessage.GetInvocationList(); - foreach (var @delegate in invocationList) - { - try - { - var messageHandledDelegate = @delegate as OnMessageDelegate; - messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); - } - catch (Exception e) - { - Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name); - } - } - } + var newEdited = parsedMessage.Encode(); + if (!Util.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)}"); + } - var newEdited = parsedMessage.Encode(); - if (!Util.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 (!Util.FastByteArrayCompare(originalMessageData, message.RawData)) + { + allocatedString = this.libcFunction.NewString(message.RawData); + Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); + messagePtr = allocatedString.Address; + } - if (!Util.FastByteArrayCompare(originalMessageData, message.RawData)) - { - allocatedString = this.libcFunction.NewString(message.RawData); - Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); - messagePtr = allocatedString.Address; - } + var newEditedSender = parsedSender.Encode(); + if (!Util.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)}"); + } - var newEditedSender = parsedSender.Encode(); - if (!Util.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 (!Util.FastByteArrayCompare(originalSenderData, sender.RawData)) + { + allocatedStringSender = this.libcFunction.NewString(sender.RawData); + Log.Debug( + $"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})"); + senderPtr = allocatedStringSender.Address; + } - if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData)) - { - allocatedStringSender = this.libcFunction.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); + } - // Print the original chat if it's handled. - if (isHandled) + 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) + { + 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))) { - 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 9a30037fa..a94dec1a7 100644 --- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs @@ -1,104 +1,103 @@ using System; -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. + /// Gets the address of the native PrintMessage method. /// - public sealed class ChatGuiAddressResolver : BaseAddressResolver + 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 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 + */ + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the address of the native PrintMessage method. - /// - public IntPtr PrintMessage { get; private set; } + // 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 - /// - /// Gets the address of the native PopulateItemLinkObject method. - /// - public IntPtr PopulateItemLinkObject { get; private set; } + // 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"); - /// - /// Gets the address of the native InteractableLinkClicked method. - /// - public IntPtr InteractableLinkClicked { get; private set; } + // 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"); - /* - --- 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 - */ + // 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"); - /// - 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"); - - // 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"); - - 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/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index e70bad070..e351320b9 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -10,314 +10,313 @@ using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Component.GUI; using Serilog; -namespace Dalamud.Game.Gui.Dtr +namespace Dalamud.Game.Gui.Dtr; + +/// +/// Class used to interface with the server info bar. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed unsafe class DtrBar : IDisposable, IServiceType { - /// - /// Class used to interface with the server info bar. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe class DtrBar : IDisposable, IServiceType + private const uint BaseNodeId = 1000; + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly GameGui gameGui = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration configuration = Service.Get(); + + private List entries = new(); + private uint runningNodeIds = BaseNodeId; + + [ServiceManager.ServiceConstructor] + private DtrBar() { - private const uint BaseNodeId = 1000; + this.framework.Update += this.Update; - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); + this.configuration.DtrOrder ??= new List(); + this.configuration.DtrIgnore ??= new List(); + this.configuration.Save(); + } - [ServiceManager.ServiceDependency] - private readonly GameGui gameGui = Service.Get(); + /// + /// Get a DTR bar entry. + /// This allows you to add your own text, and users to sort it. + /// + /// A user-friendly name for sorting. + /// The text the entry shows. + /// The entry object used to update, hide and remove the entry. + /// Thrown when an entry with the specified title exists. + public DtrBarEntry Get(string title, SeString? text = null) + { + if (this.entries.Any(x => x.Title == title)) + throw new ArgumentException("An entry with the same title already exists."); - [ServiceManager.ServiceDependency] - private readonly DalamudConfiguration configuration = Service.Get(); + var node = this.MakeNode(++this.runningNodeIds); + var entry = new DtrBarEntry(title, node); + entry.Text = text; - private List entries = new(); - private uint runningNodeIds = BaseNodeId; + // Add the entry to the end of the order list, if it's not there already. + if (!this.configuration.DtrOrder!.Contains(title)) + this.configuration.DtrOrder!.Add(title); + this.entries.Add(entry); + this.ApplySort(); - [ServiceManager.ServiceConstructor] - private DtrBar() + return entry; + } + + /// + void IDisposable.Dispose() + { + foreach (var entry in this.entries) + this.RemoveNode(entry.TextNode); + + this.entries.Clear(); + this.framework.Update -= this.Update; + } + + /// + /// Remove nodes marked as "should be removed" from the bar. + /// + internal void HandleRemovedNodes() + { + foreach (var data in this.entries.Where(d => d.ShouldBeRemoved)) { - this.framework.Update += this.Update; - - this.configuration.DtrOrder ??= new List(); - this.configuration.DtrIgnore ??= new List(); - this.configuration.Save(); + this.RemoveNode(data.TextNode); } - /// - /// Get a DTR bar entry. - /// This allows you to add your own text, and users to sort it. - /// - /// A user-friendly name for sorting. - /// The text the entry shows. - /// The entry object used to update, hide and remove the entry. - /// Thrown when an entry with the specified title exists. - public DtrBarEntry Get(string title, SeString? text = null) - { - if (this.entries.Any(x => x.Title == title)) - throw new ArgumentException("An entry with the same title already exists."); + this.entries.RemoveAll(d => d.ShouldBeRemoved); + } - var node = this.MakeNode(++this.runningNodeIds); - var entry = new DtrBarEntry(title, node); - entry.Text = text; - - // Add the entry to the end of the order list, if it's not there already. - if (!this.configuration.DtrOrder!.Contains(title)) - this.configuration.DtrOrder!.Add(title); - this.entries.Add(entry); - this.ApplySort(); - - return entry; - } - - /// - void IDisposable.Dispose() - { - foreach (var entry in this.entries) - this.RemoveNode(entry.TextNode); - - this.entries.Clear(); - this.framework.Update -= this.Update; - } - - /// - /// Remove nodes marked as "should be removed" from the bar. - /// - internal void HandleRemovedNodes() - { - foreach (var data in this.entries.Where(d => d.ShouldBeRemoved)) - { - this.RemoveNode(data.TextNode); - } - - this.entries.RemoveAll(d => d.ShouldBeRemoved); - } - - /// - /// Check whether an entry with the specified title exists. - /// - /// The title to check for. - /// Whether or not an entry with that title is registered. - internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title); - - /// - /// Dirty the DTR bar entry with the specified title. - /// - /// Title of the entry to dirty. - /// Whether the entry was found. - internal bool MakeDirty(string title) - { - var entry = this.entries.FirstOrDefault(x => x.Title == title); - if (entry == null) - return false; - - entry.Dirty = true; - return true; - } - - /// - /// Reapply the DTR entry ordering from . - /// - internal void ApplySort() - { - // Sort the current entry list, based on the order in the configuration. - var positions = this.configuration - .DtrOrder! - .Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry))) - .ToDictionary(x => x.entry, x => x.index); - - this.entries.Sort((x, y) => - { - var xPos = positions.TryGetValue(x.Title, out var xIndex) ? xIndex : int.MaxValue; - var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue; - return xPos.CompareTo(yPos); - }); - } - - private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer(); - - private void Update(Framework unused) - { - this.HandleRemovedNodes(); - - var dtr = this.GetDtr(); - if (dtr == null) return; - - // The collision node on the DTR element is always the width of its content - if (dtr->UldManager.NodeList == null) return; - - // If we have an unmodified DTR but still have entries, we need to - // work to reset our state. - if (!this.CheckForDalamudNodes()) - this.RecreateNodes(); - - var collisionNode = dtr->UldManager.NodeList[1]; - if (collisionNode == null) return; - - // If we are drawing backwards, we should start from the right side of the collision node. That is, - // collisionNode->X + collisionNode->Width. - var runningXPos = this.configuration.DtrSwapDirection - ? collisionNode->X + collisionNode->Width - : collisionNode->X; - - for (var i = 0; i < this.entries.Count; i++) - { - var data = this.entries[i]; - var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown; - - if (data.Dirty && data.Added && data.Text != null && data.TextNode != null) - { - var node = data.TextNode; - node->SetText(data.Text?.Encode()); - ushort w = 0, h = 0; - - if (isHide) - { - node->AtkResNode.ToggleVisibility(false); - } - else - { - node->AtkResNode.ToggleVisibility(true); - node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr); - node->AtkResNode.SetWidth(w); - } - - data.Dirty = false; - } - - if (!data.Added) - { - data.Added = this.AddNode(data.TextNode); - } - - if (!isHide) - { - var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing; - - if (this.configuration.DtrSwapDirection) - { - data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2); - runningXPos += elementWidth; - } - else - { - runningXPos -= elementWidth; - data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2); - } - } - - this.entries[i] = data; - } - } - - /// - /// Checks if there are any Dalamud nodes in the DTR. - /// - /// True if there are nodes with an ID > 1000. - private bool CheckForDalamudNodes() - { - var dtr = this.GetDtr(); - if (dtr == null || dtr->RootNode == null) return false; - - for (var i = 0; i < dtr->UldManager.NodeListCount; i++) - { - if (dtr->UldManager.NodeList[i]->NodeID > 1000) - return true; - } + /// + /// Check whether an entry with the specified title exists. + /// + /// The title to check for. + /// Whether or not an entry with that title is registered. + internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title); + /// + /// Dirty the DTR bar entry with the specified title. + /// + /// Title of the entry to dirty. + /// Whether the entry was found. + internal bool MakeDirty(string title) + { + var entry = this.entries.FirstOrDefault(x => x.Title == title); + if (entry == null) return false; - } - private void RecreateNodes() + entry.Dirty = true; + return true; + } + + /// + /// Reapply the DTR entry ordering from . + /// + internal void ApplySort() + { + // Sort the current entry list, based on the order in the configuration. + var positions = this.configuration + .DtrOrder! + .Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry))) + .ToDictionary(x => x.entry, x => x.index); + + this.entries.Sort((x, y) => { - this.runningNodeIds = BaseNodeId; - foreach (var entry in this.entries) + var xPos = positions.TryGetValue(x.Title, out var xIndex) ? xIndex : int.MaxValue; + var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue; + return xPos.CompareTo(yPos); + }); + } + + private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer(); + + private void Update(Framework unused) + { + this.HandleRemovedNodes(); + + var dtr = this.GetDtr(); + if (dtr == null) return; + + // The collision node on the DTR element is always the width of its content + if (dtr->UldManager.NodeList == null) return; + + // If we have an unmodified DTR but still have entries, we need to + // work to reset our state. + if (!this.CheckForDalamudNodes()) + this.RecreateNodes(); + + var collisionNode = dtr->UldManager.NodeList[1]; + if (collisionNode == null) return; + + // If we are drawing backwards, we should start from the right side of the collision node. That is, + // collisionNode->X + collisionNode->Width. + var runningXPos = this.configuration.DtrSwapDirection + ? collisionNode->X + collisionNode->Width + : collisionNode->X; + + for (var i = 0; i < this.entries.Count; i++) + { + var data = this.entries[i]; + var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown; + + if (data.Dirty && data.Added && data.Text != null && data.TextNode != null) { - entry.TextNode = this.MakeNode(++this.runningNodeIds); - entry.Added = false; - } - } + var node = data.TextNode; + node->SetText(data.Text?.Encode()); + ushort w = 0, h = 0; - private bool AddNode(AtkTextNode* node) - { - var dtr = this.GetDtr(); - if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false; + if (isHide) + { + node->AtkResNode.ToggleVisibility(false); + } + else + { + node->AtkResNode.ToggleVisibility(true); + node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr); + node->AtkResNode.SetWidth(w); + } - var lastChild = dtr->RootNode->ChildNode; - while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode; - Log.Debug($"Found last sibling: {(ulong)lastChild:X}"); - lastChild->PrevSiblingNode = (AtkResNode*)node; - node->AtkResNode.ParentNode = lastChild->ParentNode; - node->AtkResNode.NextSiblingNode = lastChild; - - dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount + 1); - Log.Debug("Set last sibling of DTR and updated child count"); - - dtr->UldManager.UpdateDrawNodeList(); - Log.Debug("Updated node draw list"); - return true; - } - - private bool RemoveNode(AtkTextNode* node) - { - var dtr = this.GetDtr(); - if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false; - - var tmpPrevNode = node->AtkResNode.PrevSiblingNode; - var tmpNextNode = node->AtkResNode.NextSiblingNode; - - // if (tmpNextNode != null) - tmpNextNode->PrevSiblingNode = tmpPrevNode; - if (tmpPrevNode != null) - tmpPrevNode->NextSiblingNode = tmpNextNode; - node->AtkResNode.Destroy(true); - - dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1); - Log.Debug("Set last sibling of DTR and updated child count"); - dtr->UldManager.UpdateDrawNodeList(); - Log.Debug("Updated node draw list"); - return true; - } - - private AtkTextNode* MakeNode(uint nodeId) - { - var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8); - if (newTextNode == null) - { - Log.Debug("Failed to allocate memory for text node"); - return null; + data.Dirty = false; } - IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode)); - newTextNode->Ctor(); + if (!data.Added) + { + data.Added = this.AddNode(data.TextNode); + } - newTextNode->AtkResNode.NodeID = nodeId; - newTextNode->AtkResNode.Type = NodeType.Text; - newTextNode->AtkResNode.Flags = (short)(NodeFlags.AnchorLeft | NodeFlags.AnchorTop); - newTextNode->AtkResNode.DrawFlags = 12; - newTextNode->AtkResNode.SetWidth(22); - newTextNode->AtkResNode.SetHeight(22); - newTextNode->AtkResNode.SetPositionFloat(-200, 2); + if (!isHide) + { + var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing; - newTextNode->LineSpacing = 12; - newTextNode->AlignmentFontType = 5; - newTextNode->FontSize = 14; - newTextNode->TextFlags = (byte)TextFlags.Edge; - newTextNode->TextFlags2 = 0; + if (this.configuration.DtrSwapDirection) + { + data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2); + runningXPos += elementWidth; + } + else + { + runningXPos -= elementWidth; + data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2); + } + } - newTextNode->SetText(" "); - - newTextNode->TextColor.R = 255; - newTextNode->TextColor.G = 255; - newTextNode->TextColor.B = 255; - newTextNode->TextColor.A = 255; - - newTextNode->EdgeColor.R = 142; - newTextNode->EdgeColor.G = 106; - newTextNode->EdgeColor.B = 12; - newTextNode->EdgeColor.A = 255; - - return newTextNode; + this.entries[i] = data; } } + + /// + /// Checks if there are any Dalamud nodes in the DTR. + /// + /// True if there are nodes with an ID > 1000. + private bool CheckForDalamudNodes() + { + var dtr = this.GetDtr(); + if (dtr == null || dtr->RootNode == null) return false; + + for (var i = 0; i < dtr->UldManager.NodeListCount; i++) + { + if (dtr->UldManager.NodeList[i]->NodeID > 1000) + return true; + } + + return false; + } + + private void RecreateNodes() + { + this.runningNodeIds = BaseNodeId; + foreach (var entry in this.entries) + { + entry.TextNode = this.MakeNode(++this.runningNodeIds); + entry.Added = false; + } + } + + private bool AddNode(AtkTextNode* node) + { + var dtr = this.GetDtr(); + if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false; + + var lastChild = dtr->RootNode->ChildNode; + while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode; + Log.Debug($"Found last sibling: {(ulong)lastChild:X}"); + lastChild->PrevSiblingNode = (AtkResNode*)node; + node->AtkResNode.ParentNode = lastChild->ParentNode; + node->AtkResNode.NextSiblingNode = lastChild; + + dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount + 1); + Log.Debug("Set last sibling of DTR and updated child count"); + + dtr->UldManager.UpdateDrawNodeList(); + Log.Debug("Updated node draw list"); + return true; + } + + private bool RemoveNode(AtkTextNode* node) + { + var dtr = this.GetDtr(); + if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false; + + var tmpPrevNode = node->AtkResNode.PrevSiblingNode; + var tmpNextNode = node->AtkResNode.NextSiblingNode; + + // if (tmpNextNode != null) + tmpNextNode->PrevSiblingNode = tmpPrevNode; + if (tmpPrevNode != null) + tmpPrevNode->NextSiblingNode = tmpNextNode; + node->AtkResNode.Destroy(true); + + dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1); + Log.Debug("Set last sibling of DTR and updated child count"); + dtr->UldManager.UpdateDrawNodeList(); + Log.Debug("Updated node draw list"); + return true; + } + + private AtkTextNode* MakeNode(uint nodeId) + { + var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8); + if (newTextNode == null) + { + Log.Debug("Failed to allocate memory for text node"); + return null; + } + + IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode)); + newTextNode->Ctor(); + + newTextNode->AtkResNode.NodeID = nodeId; + newTextNode->AtkResNode.Type = NodeType.Text; + newTextNode->AtkResNode.Flags = (short)(NodeFlags.AnchorLeft | NodeFlags.AnchorTop); + newTextNode->AtkResNode.DrawFlags = 12; + newTextNode->AtkResNode.SetWidth(22); + newTextNode->AtkResNode.SetHeight(22); + newTextNode->AtkResNode.SetPositionFloat(-200, 2); + + newTextNode->LineSpacing = 12; + newTextNode->AlignmentFontType = 5; + newTextNode->FontSize = 14; + newTextNode->TextFlags = (byte)TextFlags.Edge; + newTextNode->TextFlags2 = 0; + + newTextNode->SetText(" "); + + newTextNode->TextColor.R = 255; + newTextNode->TextColor.G = 255; + newTextNode->TextColor.B = 255; + newTextNode->TextColor.A = 255; + + newTextNode->EdgeColor.R = 142; + newTextNode->EdgeColor.G = 106; + newTextNode->EdgeColor.B = 12; + newTextNode->EdgeColor.A = 255; + + return newTextNode; + } } diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index 0c94ea352..c5bdb7e85 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -3,91 +3,90 @@ using Dalamud.Game.Text.SeStringHandling; using FFXIVClientStructs.FFXIV.Component.GUI; -namespace Dalamud.Game.Gui.Dtr +namespace Dalamud.Game.Gui.Dtr; + +/// +/// Class representing an entry in the server info bar. +/// +public sealed unsafe class DtrBarEntry : IDisposable { + private bool shownBacking = true; + private SeString? textBacking = null; + /// - /// Class representing an entry in the server info bar. + /// Initializes a new instance of the class. /// - public sealed unsafe class DtrBarEntry : IDisposable + /// The title of the bar entry. + /// The corresponding text node. + internal DtrBarEntry(string title, AtkTextNode* textNode) { - private bool shownBacking = true; - private SeString? textBacking = null; + this.Title = title; + this.TextNode = textNode; + } - /// - /// Initializes a new instance of the class. - /// - /// The title of the bar entry. - /// The corresponding text node. - internal DtrBarEntry(string title, AtkTextNode* textNode) + /// + /// Gets the title of this entry. + /// + public string Title { get; init; } + + /// + /// Gets or sets the text of this entry. + /// + public SeString? Text + { + get => this.textBacking; + set { - this.Title = title; - this.TextNode = textNode; - } - - /// - /// Gets the title of this entry. - /// - public string Title { get; init; } - - /// - /// Gets or sets the text of this entry. - /// - public SeString? Text - { - get => this.textBacking; - set - { - this.textBacking = value; - this.Dirty = true; - } - } - - /// - /// Gets or sets a value indicating whether this entry is visible. - /// - public bool Shown - { - get => this.shownBacking; - set - { - this.shownBacking = value; - this.Dirty = true; - } - } - - /// - /// Gets or sets the internal text node of this entry. - /// - internal AtkTextNode* TextNode { get; set; } - - /// - /// Gets a value indicating whether this entry should be removed. - /// - internal bool ShouldBeRemoved { get; private set; } = false; - - /// - /// Gets or sets a value indicating whether this entry is dirty. - /// - internal bool Dirty { get; set; } = false; - - /// - /// Gets or sets a value indicating whether this entry has just been added. - /// - internal bool Added { get; set; } = false; - - /// - /// Remove this entry from the bar. - /// You will need to re-acquire it from DtrBar to reuse it. - /// - public void Remove() - { - this.ShouldBeRemoved = true; - } - - /// - public void Dispose() - { - this.Remove(); + this.textBacking = value; + this.Dirty = true; } } + + /// + /// Gets or sets a value indicating whether this entry is visible. + /// + public bool Shown + { + get => this.shownBacking; + set + { + this.shownBacking = value; + this.Dirty = true; + } + } + + /// + /// Gets or sets the internal text node of this entry. + /// + internal AtkTextNode* TextNode { get; set; } + + /// + /// Gets a value indicating whether this entry should be removed. + /// + internal bool ShouldBeRemoved { get; private set; } = false; + + /// + /// Gets or sets a value indicating whether this entry is dirty. + /// + internal bool Dirty { get; set; } = false; + + /// + /// Gets or sets a value indicating whether this entry has just been added. + /// + internal bool Added { get; set; } = false; + + /// + /// Remove this entry from the bar. + /// You will need to re-acquire it from DtrBar to reuse it. + /// + public void Remove() + { + this.ShouldBeRemoved = true; + } + + /// + public void Dispose() + { + this.Remove(); + } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index cdb65fa78..cf5ca68ad 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class FlyTextGui : IDisposable, IServiceType { /// - /// 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")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class FlyTextGui : IDisposable, IServiceType + private readonly AddFlyTextDelegate addFlyTextNative; + + /// + /// The hook that fires when the game creates a fly text element. See . + /// + private readonly Hook createFlyTextHook; + + [ServiceManager.ServiceConstructor] + private FlyTextGui(SigScanner sigScanner) { - /// - /// The native function responsible for adding fly text to the UI. See . - /// - private readonly AddFlyTextDelegate addFlyTextNative; + this.Address = new FlyTextGuiAddressResolver(); + this.Address.Setup(sigScanner); - /// - /// 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 = Hook.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour); + } - [ServiceManager.ServiceConstructor] - private FlyTextGui(SigScanner sigScanner) + /// + /// 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.GetNullable(); + if (gameGui == null) + return; + + 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->GetRaptureAtkModule()->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(sigScanner); - - this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); - this.createFlyTextHook = Hook.FromAddress(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.GetNullable(); - if (gameGui == null) - return; - - 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->GetRaptureAtkModule()->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); } } - - 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; - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(GameGui gameGui) - { - this.createFlyTextHook.Enable(); - } - - 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; - } + } + + 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; + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) + { + this.createFlyTextHook.Enable(); + } + + 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 ae5e8a3fb..68650fb5c 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs @@ -1,298 +1,297 @@ -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, - /// - /// Serif Val1 with all caps condensed font ISLAND EXP with Text2 in sans-serif as subtitle. - /// - IslandExp = 15, + /// + /// Serif Val1 with all caps condensed font ISLAND EXP with Text2 in sans-serif as subtitle. + /// + IslandExp = 15, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. - /// - NamedMp = 16, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. + /// + NamedMp = 16, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. - /// - NamedTp = 17, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. + /// + NamedTp = 17, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1 (2). - /// - NamedAttack2 = 18, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1 (2). + /// + NamedAttack2 = 18, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2). - /// - NamedMp2 = 19, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2). + /// + NamedMp2 = 19, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2). - /// - NamedTp2 = 20, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2). + /// + NamedTp2 = 20, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. - /// - NamedEp = 21, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. + /// + NamedEp = 21, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle. - /// - NamedCp = 22, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle. + /// + NamedCp = 22, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle. - /// - NamedGp = 23, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle. + /// + NamedGp = 23, - /// - /// Displays nothing. - /// - None = 24, + /// + /// Displays nothing. + /// + None = 24, - /// - /// All caps serif INVULNERABLE. - /// - Invulnerable = 25, + /// + /// All caps serif INVULNERABLE. + /// + Invulnerable = 25, - /// - /// All caps sans-serif condensed font INTERRUPTED! - /// Does a large bounce effect on appearance. - /// Does not scroll up or down the screen. - /// - Interrupted = 26, + /// + /// All caps sans-serif condensed font INTERRUPTED! + /// Does a large bounce effect on appearance. + /// Does not scroll up or down the screen. + /// + Interrupted = 26, - /// - /// AutoAttack with no Text2. - /// - AutoAttackNoText = 27, + /// + /// AutoAttack with no Text2. + /// + AutoAttackNoText = 27, - /// - /// AutoAttack with no Text2 (2). - /// - AutoAttackNoText2 = 28, + /// + /// AutoAttack with no Text2 (2). + /// + AutoAttackNoText2 = 28, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2). - /// - CriticalHit2 = 29, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2). + /// + CriticalHit2 = 29, - /// - /// AutoAttack with no Text2 (3). - /// - AutoAttackNoText3 = 30, + /// + /// AutoAttack with no Text2 (3). + /// + AutoAttackNoText3 = 30, - /// - /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). - /// - NamedCriticalHit2 = 31, + /// + /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). + /// + NamedCriticalHit2 = 31, - /// - /// 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 = 32, + /// + /// 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 = 32, - /// - /// 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 = 33, + /// + /// 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 = 33, - /// - /// Same as NamedIcon with sans-serif "has no effect!" to the right. - /// - NamedIconHasNoEffect = 34, + /// + /// Same as NamedIcon with sans-serif "has no effect!" to the right. + /// + NamedIconHasNoEffect = 34, - /// - /// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration. - /// - NamedIconFaded = 35, + /// + /// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration. + /// + NamedIconFaded = 35, - /// - /// Same as NamedIcon but Text1 is slightly faded (2). - /// Used for buff expiration. - /// - NamedIconFaded2 = 36, + /// + /// Same as NamedIcon but Text1 is slightly faded (2). + /// Used for buff expiration. + /// + NamedIconFaded2 = 36, - /// - /// Text1 in sans-serif font. - /// - Named = 37, + /// + /// Text1 in sans-serif font. + /// + Named = 37, - /// - /// Same as NamedIcon with sans-serif "(fully resisted)" to the right. - /// - NamedIconFullyResisted = 38, + /// + /// Same as NamedIcon with sans-serif "(fully resisted)" to the right. + /// + NamedIconFullyResisted = 38, - /// - /// All caps serif 'INCAPACITATED!'. - /// - Incapacitated = 39, + /// + /// All caps serif 'INCAPACITATED!'. + /// + Incapacitated = 39, - /// - /// Text1 with sans-serif "(fully resisted)" to the right. - /// - NamedFullyResisted = 40, + /// + /// Text1 with sans-serif "(fully resisted)" to the right. + /// + NamedFullyResisted = 40, - /// - /// Text1 with sans-serif "has no effect!" to the right. - /// - NamedHasNoEffect = 41, + /// + /// Text1 with sans-serif "has no effect!" to the right. + /// + NamedHasNoEffect = 41, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1 (3). - /// - NamedAttack3 = 42, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1 (3). + /// + NamedAttack3 = 42, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3). - /// - NamedMp3 = 43, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3). + /// + NamedMp3 = 43, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3). - /// - NamedTp3 = 44, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3). + /// + NamedTp3 = 44, - /// - /// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1. - /// - NamedIconInvulnerable = 45, + /// + /// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1. + /// + NamedIconInvulnerable = 45, - /// - /// All caps serif RESIST. - /// - Resist = 46, + /// + /// All caps serif RESIST. + /// + Resist = 46, - /// - /// Same as NamedIcon but places the given icon in the item icon outline. - /// - NamedIconWithItemOutline = 47, + /// + /// Same as NamedIcon but places the given icon in the item icon outline. + /// + NamedIconWithItemOutline = 47, - /// - /// AutoAttack with no Text2 (4). - /// - AutoAttackNoText4 = 48, + /// + /// AutoAttack with no Text2 (4). + /// + AutoAttackNoText4 = 48, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3). - /// Does a bigger bounce effect on appearance. - /// - CriticalHit3 = 49, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3). + /// Does a bigger bounce effect on appearance. + /// + CriticalHit3 = 49, - /// - /// All caps serif REFLECT. - /// - Reflect = 50, + /// + /// All caps serif REFLECT. + /// + Reflect = 50, - /// - /// All caps serif REFLECTED. - /// - Reflected = 51, + /// + /// All caps serif REFLECTED. + /// + Reflected = 51, - /// - /// Val1 in serif font, Text2 in sans-serif as subtitle (2). - /// Does a bounce effect on appearance. - /// - DirectHit2 = 52, + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle (2). + /// Does a bounce effect on appearance. + /// + DirectHit2 = 52, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4). - /// Does a bigger bounce effect on appearance. - /// - CriticalHit4 = 53, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4). + /// Does a bigger bounce effect on appearance. + /// + CriticalHit4 = 53, - /// - /// 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 = 54, - } + /// + /// 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 = 54, } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 3576c66a0..177c01fb9 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -14,563 +14,562 @@ using FFXIVClientStructs.FFXIV.Component.GUI; 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed unsafe class GameGui : IDisposable, IServiceType { - /// - /// A class handling many aspects of the in-game UI. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe class GameGui : IDisposable, IServiceType + private readonly GameGuiAddressResolver address; + + private readonly GetMatrixSingletonDelegate getMatrixSingleton; + private readonly ScreenToWorldNativeDelegate screenToWorldNative; + + 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 readonly Hook utf8StringFromSequenceHook; + + private GetUIMapObjectDelegate getUIMapObject; + private OpenMapWithFlagDelegate openMapWithFlag; + + [ServiceManager.ServiceConstructor] + private GameGui(SigScanner sigScanner) { - private readonly GameGuiAddressResolver address; + this.address = new GameGuiAddressResolver(); + this.address.Setup(sigScanner); - private readonly GetMatrixSingletonDelegate getMatrixSingleton; - private readonly ScreenToWorldNativeDelegate screenToWorldNative; + 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}"); - 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 readonly Hook utf8StringFromSequenceHook; + this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - private GetUIMapObjectDelegate getUIMapObject; - private OpenMapWithFlagDelegate openMapWithFlag; + this.handleItemHoverHook = Hook.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour); + this.handleItemOutHook = Hook.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour); - [ServiceManager.ServiceConstructor] - private GameGui(SigScanner sigScanner) + this.handleActionHoverHook = Hook.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); + this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); + + this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); + + this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton); + + this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld); + + this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour); + + this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour); + } + + // 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); + + // Hooked delegates + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen); + + [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(sigScanner); - - 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}"); - - this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - - this.handleItemHoverHook = Hook.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour); - this.handleItemOutHook = Hook.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour); - - this.handleActionHoverHook = Hook.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); - this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); - - this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); - - this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton); - - this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld); - - this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour); - - this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour); + 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); - - // Hooked delegates - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen); - - [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 addonPtr) - { - if (addonPtr == IntPtr.Zero) - return IntPtr.Zero; - - var uiModule = (UIModule*)this.GetUIModule(); - if (uiModule == null) - return IntPtr.Zero; - - var agentModule = uiModule->GetAgentModule(); - if (agentModule == null) - return IntPtr.Zero; - - var addon = (AtkUnitBase*)addonPtr; - var addonId = addon->ParentID == 0 ? addon->ID : addon->ParentID; - - if (addonId == 0) - return IntPtr.Zero; - - var index = 0; - while (true) + worldPos = new Vector3 { - var agent = agentModule->GetAgentByInternalID((uint)index++); - if (agent == uiModule || agent == null) - break; + X = worldPosArray[0], + Y = worldPosArray[1], + Z = worldPosArray[2], + }; + } - if (agent->AddonId == addonId) - return new IntPtr(agent); - } + 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 addonPtr) + { + if (addonPtr == IntPtr.Zero) + return IntPtr.Zero; + + var uiModule = (UIModule*)this.GetUIModule(); + if (uiModule == null) + return IntPtr.Zero; + + var agentModule = uiModule->GetAgentModule(); + if (agentModule == null) + return IntPtr.Zero; + + var addon = (AtkUnitBase*)addonPtr; + var addonId = addon->ParentID == 0 ? addon->ID : addon->ParentID; + + if (addonId == 0) + return IntPtr.Zero; + + var index = 0; + while (true) + { + var agent = agentModule->GetAgentByInternalID((uint)index++); + if (agent == uiModule || agent == null) + break; + + if (agent->AddonId == addonId) + return new IntPtr(agent); } - /// - /// Disables the hooks and submodules of this module. - /// - void IDisposable.Dispose() + return IntPtr.Zero; + } + + /// + /// Disables the hooks and submodules of this module. + /// + void IDisposable.Dispose() + { + this.setGlobalBgmHook.Dispose(); + this.handleItemHoverHook.Dispose(); + this.handleItemOutHook.Dispose(); + this.handleImmHook.Dispose(); + this.toggleUiHideHook.Dispose(); + this.handleActionHoverHook.Dispose(); + this.handleActionOutHook.Dispose(); + this.utf8StringFromSequenceHook.Dispose(); + } + + /// + /// Set the current background music. + /// + /// The background music key. + public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); + + /// + /// Reset the stored "UI hide" state. + /// + internal void ResetUiHideState() + { + this.GameUiHidden = false; + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.setGlobalBgmHook.Enable(); + this.handleItemHoverHook.Enable(); + this.handleItemOutHook.Enable(); + this.handleImmHook.Enable(); + this.toggleUiHideHook.Enable(); + this.handleActionHoverHook.Enable(); + this.handleActionOutHook.Enable(); + this.utf8StringFromSequenceHook.Enable(); + } + + 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) { - this.setGlobalBgmHook.Dispose(); - this.handleItemHoverHook.Dispose(); - this.handleItemOutHook.Dispose(); - this.handleImmHook.Dispose(); - this.toggleUiHideHook.Dispose(); - this.handleActionHoverHook.Dispose(); - this.handleActionOutHook.Dispose(); - this.utf8StringFromSequenceHook.Dispose(); + var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); + this.HoveredItem = itemId; + + this.HoveredItemChanged?.InvokeSafely(this, itemId); + + Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X")); } - /// - /// Set the current background music. - /// - /// The background music key. - public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); + return retVal; + } - /// - /// Reset the stored "UI hide" state. - /// - internal void ResetUiHideState() + 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) { - this.GameUiHidden = false; - } + var a3Val = Marshal.ReadByte(a3, 0x8); - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction() - { - this.setGlobalBgmHook.Enable(); - this.handleItemHoverHook.Enable(); - this.handleItemOutHook.Enable(); - this.handleImmHook.Enable(); - this.toggleUiHideHook.Enable(); - this.handleActionHoverHook.Enable(); - this.handleActionOutHook.Enable(); - this.utf8StringFromSequenceHook.Enable(); - } - - 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) + if (a3Val == 255) { - var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); - this.HoveredItem = itemId; + this.HoveredItem = 0ul; - this.HoveredItemChanged?.InvokeSafely(this, itemId); - - 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) + try { - this.HoveredItem = 0ul; - - try - { - this.HoveredItemChanged?.Invoke(this, 0ul); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } - - Log.Verbose("HoverItemId: 0"); + this.HoveredItemChanged?.Invoke(this, 0ul); } - } - - 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); - this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); - - 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) + catch (Exception e) { - 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"); + Log.Error(e, "Could not dispatch HoveredItemChanged event."); } + + Log.Verbose("HoverItemId: 0"); } - - return retVal; } - 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); + this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); + + 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) { - // TODO(goat): We should read this from memory directly, instead of relying on catching every toggle. - this.GameUiHidden = !this.GameUiHidden; + var a3Val = Marshal.ReadByte(a3, 0x8); - this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden); + if (a3Val == 255) + { + this.HoveredAction.ActionKind = HoverActionKind.None; + this.HoveredAction.BaseActionID = 0; + this.HoveredAction.ActionID = 0; - Log.Debug("UiHide toggled: {0}", this.GameUiHidden); + try + { + this.HoveredActionChanged?.Invoke(this, this.HoveredAction); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredActionChanged event."); + } - return this.toggleUiHideHook.Original(thisPtr, unknownByte); + Log.Verbose("HoverActionId: 0"); + } } - private char HandleImmDetour(IntPtr framework, char a2, byte a3) - { - var result = this.handleImmHook.Original(framework, a2, a3); - return ImGui.GetIO().WantTextInput - ? (char)0 - : result; - } + return retVal; + } - private Utf8String* Utf8StringFromSequenceDetour(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen) - { - if (sourcePtr != null) - this.utf8StringFromSequenceHook.Original(thisPtr, sourcePtr, sourceLen); - else - thisPtr->Ctor(); // this is in clientstructs but you could do it manually too + private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) + { + // TODO(goat): We should read this from memory directly, instead of relying on catching every toggle. + this.GameUiHidden = !this.GameUiHidden; - return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe? - } + this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden); + + 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; + } + + private Utf8String* Utf8StringFromSequenceDetour(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen) + { + if (sourcePtr != null) + this.utf8StringFromSequenceHook.Original(thisPtr, sourcePtr, sourceLen); + else + thisPtr->Ctor(); // this is in clientstructs but you could do it manually too + + return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe? } } diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index ae36fe31b..fda4acb99 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -1,80 +1,79 @@ using System; -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. + /// Gets the base address of the native GuiManager class. /// - internal sealed class GameGuiAddressResolver : BaseAddressResolver + public IntPtr BaseAddress { 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 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 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 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 ToggleUiHide method. + /// + public IntPtr ToggleUiHide { get; private set; } + + /// + /// Gets the address of the native Utf8StringFromSequence method. + /// + public IntPtr Utf8StringFromSequence { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the base address of the native GuiManager class. - /// - public IntPtr BaseAddress { 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 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 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 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 ToggleUiHide method. - /// - public IntPtr ToggleUiHide { get; private set; } - - /// - /// Gets the address of the native Utf8StringFromSequence method. - /// - public IntPtr Utf8StringFromSequence { 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 ?? ?? ?? ??"); - this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8"); - } + 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 ?? ?? ?? ??"); + this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8"); } } 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 4304bb791..37c072806 100644 --- a/Dalamud/Game/Gui/Internal/DalamudIME.cs +++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs @@ -14,261 +14,261 @@ using PInvoke; using static Dalamud.NativeFunctions; -namespace Dalamud.Game.Gui.Internal +namespace Dalamud.Game.Gui.Internal; + +/// +/// This class handles IME for non-English users. +/// +[ServiceManager.EarlyLoadedService] +internal unsafe class DalamudIME : IDisposable, IServiceType { - /// - /// This class handles IME for non-English users. - /// - [ServiceManager.EarlyLoadedService] - internal unsafe class DalamudIME : IDisposable, IServiceType + private static readonly ModuleLog Log = new("IME"); + + private AsmHook imguiTextInputCursorHook; + private Vector2* cursorPos; + + [ServiceManager.ServiceConstructor] + private DalamudIME() { - private static readonly ModuleLog Log = new("IME"); + } - private AsmHook imguiTextInputCursorHook; - private Vector2* cursorPos; + /// + /// Gets a value indicating whether the module is enabled. + /// + internal bool IsEnabled { get; private set; } - [ServiceManager.ServiceConstructor] - private DalamudIME() + /// + /// 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() + { + this.imguiTextInputCursorHook?.Dispose(); + Marshal.FreeHGlobal((IntPtr)this.cursorPos); + } + + /// + /// Processes window messages. + /// + /// Handle of the window. + /// Type of window message. + /// wParam or the pointer to it. + /// lParam or the pointer to it. + /// Return value, if not doing further processing. + public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParamPtr, void* lParamPtr) + { + try { - } - - /// - /// 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() - { - this.imguiTextInputCursorHook?.Dispose(); - Marshal.FreeHGlobal((IntPtr)this.cursorPos); - } - - /// - /// Processes window messages. - /// - /// Handle of the window. - /// Type of window message. - /// wParam or the pointer to it. - /// lParam or the pointer to it. - /// Return value, if not doing further processing. - public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParamPtr, void* lParamPtr) - { - try + if (ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput) { - if (ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput) + var io = ImGui.GetIO(); + var wmsg = (WindowsMessage)msg; + long wParam = (long)wParamPtr, lParam = (long)lParamPtr; + try { - var io = ImGui.GetIO(); - var wmsg = (WindowsMessage)msg; - long wParam = (long)wParamPtr, lParam = (long)lParamPtr; - try - { - wParam = Marshal.ReadInt32((IntPtr)wParamPtr); - } - catch - { - // ignored - } + wParam = Marshal.ReadInt32((IntPtr)wParamPtr); + } + catch + { + // ignored + } - try - { - lParam = Marshal.ReadInt32((IntPtr)lParamPtr); - } - catch - { - // ignored - } + try + { + lParam = Marshal.ReadInt32((IntPtr)lParamPtr); + } + catch + { + // ignored + } - switch (wmsg) - { - case WindowsMessage.WM_IME_NOTIFY: - switch ((IMECommand)(IntPtr)wParam) - { - case IMECommand.ChangeCandidate: - this.ToggleWindow(true); - this.LoadCand(hWnd); - 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 (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause | - IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & (long)(IntPtr)lParam) > 0) - { - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return IntPtr.Zero; - - 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); - } - else - { - this.LoadCand(hWnd); - } - } - - if (((long)(IntPtr)lParam & (long)IMEComposition.ResultStr) > 0) - { - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return IntPtr.Zero; - - 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; + switch (wmsg) + { + case WindowsMessage.WM_IME_NOTIFY: + switch ((IMECommand)(IntPtr)wParam) + { + case IMECommand.ChangeCandidate: + this.ToggleWindow(true); + this.LoadCand(hWnd); + break; + case IMECommand.OpenCandidate: + this.ToggleWindow(true); this.ImmCandNative = default; - this.ImmCand.Clear(); + // 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 (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause | + IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & (long)(IntPtr)lParam) > 0) + { + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return IntPtr.Zero; + + 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); } + else + { + this.LoadCand(hWnd); + } + } - break; + if (((long)(IntPtr)lParam & (long)IMEComposition.ResultStr) > 0) + { + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return IntPtr.Zero; - default: - break; - } + 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); + } + + break; + + default: + break; } } - catch (Exception ex) + } + catch (Exception ex) + { + Log.Error(ex, "Prevented a crash in an IME hook"); + } + + return null; + } + + /// + /// Get the position of the cursor. + /// + /// The position of the cursor. + internal Vector2 GetCursorPos() + { + return new Vector2(this.cursorPos->X, this.cursorPos->Y); + } + + private unsafe void LoadCand(IntPtr hWnd) + { + if (hWnd == IntPtr.Zero) + return; + + var hImc = ImmGetContext(hWnd); + if (hImc == IntPtr.Zero) + return; + + var size = ImmGetCandidateListW(hImc, 0, IntPtr.Zero, 0); + if (size == 0) + return; + + 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++) { - Log.Error(ex, "Prevented a crash in an IME hook"); + dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int))); } - return null; - } + var pageStart = candlist.PageStart; - /// - /// Get the position of the cursor. - /// - /// The position of the cursor. - internal Vector2 GetCursorPos() - { - return new Vector2(this.cursorPos->X, this.cursorPos->Y); - } + var cand = new string[pageSize]; + this.ImmCand.Clear(); - private unsafe void LoadCand(IntPtr hWnd) - { - if (hWnd == IntPtr.Zero) - return; - - var hImc = ImmGetContext(hWnd); - if (hImc == IntPtr.Zero) - return; - - var size = ImmGetCandidateListW(hImc, 0, IntPtr.Zero, 0); - if (size == 0) - return; - - 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) + for (var i = 0; i < pageSize; i++) { - var dwOffsets = new int[candCount]; - for (var i = 0; i < candCount; 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) { - dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int))); + var candBytes = new byte[len]; + Marshal.Copy(pStrStart, candBytes, 0, len); + + var candStr = Encoding.Unicode.GetString(candBytes); + cand[i] = candStr; + + this.ImmCand.Add(candStr); } - - 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); } + + Marshal.FreeHGlobal(candlistPtr); } + } - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene) + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene) + { + try { - try + var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "cimgui.dll"); + var scanner = new SigScanner(module); + var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF"); + Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}"); + + this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2)); + this.cursorPos->X = 0f; + this.cursorPos->Y = 0f; + + var asm = new[] { - var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "cimgui.dll"); - var scanner = new SigScanner(module); - var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF"); - Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}"); - - this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2)); - this.cursorPos->X = 0f; - this.cursorPos->Y = 0f; - - var asm = new[] - { "use64", $"push rax", $"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}", @@ -276,27 +276,26 @@ namespace Dalamud.Game.Gui.Internal $"mov rax, {(IntPtr)this.cursorPos}", $"movss [rax],xmm6", $"pop rax", - }; + }; - Log.Debug($"Asm Code:\n{string.Join("\n", asm)}"); - this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook"); - this.imguiTextInputCursorHook?.Enable(); + Log.Debug($"Asm Code:\n{string.Join("\n", asm)}"); + this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook"); + this.imguiTextInputCursorHook?.Enable(); - this.IsEnabled = true; - Log.Information("Enabled!"); - } - catch (Exception ex) - { - Log.Information(ex, "Enable failed"); - } + this.IsEnabled = true; + Log.Information("Enabled!"); } - - private void ToggleWindow(bool visible) + catch (Exception ex) { - if (visible) - Service.GetNullable()?.OpenImeWindow(); - else - Service.GetNullable()?.CloseImeWindow(); + Log.Information(ex, "Enable failed"); } } + + private void ToggleWindow(bool visible) + { + if (visible) + Service.GetNullable()?.OpenImeWindow(); + else + Service.GetNullable()?.CloseImeWindow(); + } } 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 c6a7dad04..6427f2a54 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -8,134 +8,133 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class PartyFinderGui : IDisposable, IServiceType { + 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")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class PartyFinderGui : IDisposable, IServiceType + /// Sig scanner to use. + [ServiceManager.ServiceConstructor] + private PartyFinderGui(SigScanner sigScanner) { - private readonly PartyFinderAddressResolver address; - private readonly IntPtr memory; + this.address = new PartyFinderAddressResolver(); + this.address.Setup(sigScanner); - private readonly Hook receiveListingHook; + this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); - /// - /// Initializes a new instance of the class. - /// - /// Sig scanner to use. - [ServiceManager.ServiceConstructor] - private PartyFinderGui(SigScanner sigScanner) + this.receiveListingHook = Hook.FromAddress(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; + + /// + /// Dispose of managed and unmanaged resources. + /// + void IDisposable.Dispose() + { + this.receiveListingHook.Dispose(); + + try { - this.address = new PartyFinderAddressResolver(); - this.address.Setup(sigScanner); + Marshal.FreeHGlobal(this.memory); + } + catch (BadImageFormatException) + { + Log.Warning("Could not free PartyFinderGui memory."); + } + } - this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) + { + this.receiveListingHook.Enable(); + } - this.receiveListingHook = Hook.FromAddress(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); - /// - /// Dispose of managed and unmanaged resources. - /// - void IDisposable.Dispose() + // 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.Dispose(); + // these are empty slots that are not shown to the player + if (packet.Listings[i].IsNull()) + { + continue; + } - try + var listing = new PartyFinderListing(packet.Listings[i]); + var args = new PartyFinderListingEventArgs(packet.BatchNumber); + this.ReceiveListing?.Invoke(listing, args); + + if (args.Visible) { - Marshal.FreeHGlobal(this.memory); - } - catch (BadImageFormatException) - { - Log.Warning("Could not free PartyFinderGui memory."); + continue; } + + // hide the listing from the player by setting it to a null listing + packet.Listings[i] = default; + needToRewrite = true; } - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(GameGui gameGui) + if (!needToRewrite) { - this.receiveListingHook.Enable(); + 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 50f59426e..d9be66856 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs @@ -1,32 +1,31 @@ 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 << 0, + None = 1 << 0, - /// - /// The duty complete condition. - /// - DutyComplete = 1 << 1, + /// + /// The duty complete condition. + /// + DutyComplete = 1 << 1, - /// - /// The duty complete (weekly reward unclaimed) condition. This condition is - /// only available for savage fights prior to echo release. - /// - DutyCompleteWeeklyRewardUnclaimed = 1 << 3, + /// + /// The duty complete (weekly reward unclaimed) condition. This condition is + /// only available for savage fights prior to echo release. + /// + DutyCompleteWeeklyRewardUnclaimed = 1 << 3, - /// - /// The duty incomplete condition. - /// - DutyIncomplete = 1 << 2, - } + /// + /// The duty incomplete condition. + /// + DutyIncomplete = 1 << 2, } 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 b79e374d9..9d6c8820c 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs @@ -1,156 +1,155 @@ 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, - /// - /// Reaper (RPR). - /// - Reaper = 1 << 28, + /// + /// Reaper (RPR). + /// + Reaper = 1 << 28, - /// - /// Sage (SGE). - /// - Sage = 1 << 29, - } + /// + /// Sage (SGE). + /// + Sage = 1 << 29, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs index 67469f6aa..c7630acfa 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs @@ -1,58 +1,57 @@ 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, + JobFlags.Reaper => 39, + JobFlags.Sage => 40, + _ => 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, - JobFlags.Reaper => 39, - JobFlags.Sage => 40, - _ => 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..503b7d905 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..a0853adf1 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 05954553a..e65fa1444 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -7,429 +7,428 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed partial class ToastGui : IDisposable, IServiceType +{ + 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. + /// + /// Sig scanner to use. + [ServiceManager.ServiceConstructor] + private ToastGui(SigScanner sigScanner) + { + this.address = new ToastGuiAddressResolver(); + this.address.Setup(sigScanner); + + this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); + this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); + this.showErrorToastHook = Hook.FromAddress(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 + + /// + /// Disposes of managed and unmanaged resources. + /// + void IDisposable.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; + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) + { + this.showNormalToastHook.Enable(); + this.showQuestToastHook.Enable(); + this.showErrorToastHook.Enable(); + } + + 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")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class ToastGui : IDisposable, IServiceType + /// 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. - /// - /// Sig scanner to use. - [ServiceManager.ServiceConstructor] - private ToastGui(SigScanner sigScanner) - { - this.address = new ToastGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); - this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); - this.showErrorToastHook = Hook.FromAddress(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 - - /// - /// Disposes of managed and unmanaged resources. - /// - void IDisposable.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; - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(GameGui gameGui) - { - this.showNormalToastHook.Enable(); - this.showQuestToastHook.Enable(); - this.showErrorToastHook.Enable(); - } - - 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.GetNullable()?.GetUIModule(); + if (manager == null) + return; + + // 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.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + fixed (byte* ptr = terminated) { - fixed (byte* ptr = terminated) - { - this.HandleNormalToastDetour(manager!.Value, (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!.Value, (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.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // 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!.Value, - (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.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - - unsafe - { - fixed (byte* ptr = terminated) - { - this.HandleErrorToastDetour(manager!.Value, (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.GetNullable()?.GetUIModule(); + if (manager == null) + return; + + // terminate the string + var terminated = Terminate(bytes); + + var (ioc1, ioc2) = this.DetermineParameterOrder(options); + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleQuestToastDetour( + manager!.Value, + (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.GetNullable()?.GetUIModule(); + if (manager == null) + return; + + // terminate the string + var terminated = Terminate(bytes); + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleErrorToastDetour(manager!.Value, (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 3b84b6cce..ba482ef48 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -6,126 +6,125 @@ using Dalamud.Configuration.Internal; #endif using Serilog; -namespace Dalamud.Game.Internal +namespace Dalamud.Game.Internal; + +/// +/// This class disables anti-debug functionality in the game client. +/// +[ServiceManager.EarlyLoadedService] +internal sealed partial class AntiDebug : IServiceType { - /// - /// This class disables anti-debug functionality in the game client. - /// - [ServiceManager.EarlyLoadedService] - internal sealed partial class AntiDebug : IServiceType + private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; + private byte[] original; + private IntPtr debugCheckAddress; + + [ServiceManager.ServiceConstructor] + private AntiDebug(SigScanner sigScanner) { - private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; - private byte[] original; - private IntPtr debugCheckAddress; - - [ServiceManager.ServiceConstructor] - private AntiDebug(SigScanner sigScanner) + try { - try - { - this.debugCheckAddress = sigScanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); - } - catch (KeyNotFoundException) - { - this.debugCheckAddress = IntPtr.Zero; - } + this.debugCheckAddress = sigScanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); + } + catch (KeyNotFoundException) + { + this.debugCheckAddress = IntPtr.Zero; + } - Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); + Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); - if (!this.IsEnabled) - { + if (!this.IsEnabled) + { #if DEBUG this.Enable(); #else - if (Service.Get().IsAntiAntiDebugEnabled) - this.Enable(); + if (Service.Get().IsAntiAntiDebugEnabled) + this.Enable(); #endif - } - } - - /// - /// 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; } } /// - /// 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 502390ca7..94d5e3be6 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs @@ -7,97 +7,96 @@ using Dalamud.Game.Internal.DXGI.Definitions; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; 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; } + Device* kernelDev; + SwapChain* swapChain; + void* dxgiSwapChain; - /// - public IntPtr ResizeBuffers { get; set; } - - /// - /// Gets a value indicating whether or not ReShade is loaded/used. - /// - public bool IsReshade { get; private set; } - - /// - protected override unsafe void Setup64Bit(SigScanner sig) + while (true) { - Device* kernelDev; - SwapChain* swapChain; - void* dxgiSwapChain; + kernelDev = Device.Instance(); + if (kernelDev == null) + continue; - while (true) + swapChain = kernelDev->SwapChain; + if (swapChain == null) + continue; + + dxgiSwapChain = swapChain->DXGISwapChain; + if (dxgiSwapChain == null) + continue; + + break; + } + + var scVtbl = GetVTblAddresses(new IntPtr(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")) { - kernelDev = Device.Instance(); - if (kernelDev == null) - continue; + // reshade master@4232872 RVA + // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present + // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present - swapChain = kernelDev->SwapChain; - if (swapChain == null) - continue; - - dxgiSwapChain = swapChain->DXGISwapChain; - if (dxgiSwapChain == null) - continue; - - break; - } - - var scVtbl = GetVTblAddresses(new IntPtr(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")) + 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 cf2af2eb5..9ed5d7ab9 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -14,265 +14,264 @@ 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. +/// +[ServiceManager.EarlyLoadedService] +internal sealed unsafe partial class DalamudAtkTweaks : IServiceType { - /// - /// This class implements in-game Dalamud options in the in-game System menu. - /// - [ServiceManager.EarlyLoadedService] - internal sealed unsafe partial class DalamudAtkTweaks : IServiceType + 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; + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration configuration = Service.Get(); + + // [ServiceManager.ServiceDependency] + // private readonly ContextMenu contextMenu = Service.Get(); + + private readonly string locDalamudPlugins; + private readonly string locDalamudSettings; + + [ServiceManager.ServiceConstructor] + private DalamudAtkTweaks(SigScanner sigScanner) { - private readonly AtkValueChangeType atkValueChangeType; - private readonly AtkValueSetString atkValueSetString; - private readonly Hook hookAgentHudOpenSystemMenu; + var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); - // TODO: Make this into events in Framework.Gui - private readonly Hook hookUiModuleRequestMainCommand; + this.hookAgentHudOpenSystemMenu = Hook.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); - private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); + this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer(atkValueChangeTypeAddress); - [ServiceManager.ServiceDependency] - private readonly DalamudConfiguration configuration = Service.Get(); + var atkValueSetStringAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); + this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress); - // [ServiceManager.ServiceDependency] - // private readonly ContextMenu contextMenu = Service.Get(); + 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 = Hook.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - private readonly string locDalamudPlugins; - private readonly string locDalamudSettings; + 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 = Hook.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); - [ServiceManager.ServiceConstructor] - private DalamudAtkTweaks(SigScanner sigScanner) - { - var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); + this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins"); + this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); - this.hookAgentHudOpenSystemMenu = Hook.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); - - 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 = Hook.FromAddress(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 = Hook.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); - - this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins"); - this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); - - // this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; - } - - 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); - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(DalamudInterface dalamudInterface) - { - this.hookAgentHudOpenSystemMenu.Enable(); - this.hookUiModuleRequestMainCommand.Enable(); - this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); - } - - /* - private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args) - { - var systemText = Service.GetNullable()?.GetExcelSheet()?.GetRow(1059)?.Text?.RawString; // "System" - var interfaceManager = Service.GetNullable(); - - if (systemText == null || interfaceManager == null) - return; - - if (args.Title == systemText && this.configuration.DoButtonsSystemMenu && interfaceManager.IsDispatchingEvents) - { - var dalamudInterface = Service.Get(); - - args.Items.Insert(0, new CustomContextMenuItem(this.locDalamudSettings, selectedArgs => - { - dalamudInterface.ToggleSettingsWindow(); - })); - - args.Items.Insert(0, new CustomContextMenuItem(this.locDalamudPlugins, selectedArgs => - { - dalamudInterface.TogglePluginInstallerWindow(); - })); - } - } - */ - - 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 && this.configuration.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 && this.configuration.IsFocusManagementEnabled) - { - Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return; - } - - var interfaceManager = Service.GetNullable(); - if (interfaceManager == null) - { - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); - return; - } - - if (!this.configuration.DoButtonsSystemMenu || !interfaceManager.IsDispatchingEvents) - { - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); - return; - } - - // the max size (hardcoded) is 0x11/17, 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 >= 0x11) - { - 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 17 slots are the MainCommand - // the 17 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 + 17]; - this.atkValueChangeType(firstStringEntry, ValueType.String); - var secondStringEntry = &atkValueArgs[6 + 17]; - this.atkValueChangeType(secondStringEntry, ValueType.String); - - const int color = 539; - var strPlugins = new SeString().Append(new UIForegroundPayload(color)) - .Append($"{SeIconChar.BoxedLetterD.ToIconString()} ") - .Append(new UIForegroundPayload(0)) - .Append(this.locDalamudPlugins).Encode(); - var strSettings = new SeString().Append(new UIForegroundPayload(color)) - .Append($"{SeIconChar.BoxedLetterD.ToIconString()} ") - .Append(new UIForegroundPayload(0)) - .Append(this.locDalamudSettings).Encode(); - - // 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.GetNullable(); - - switch (commandId) - { - case 69420: - dalamudInterface?.TogglePluginInstallerWindow(); - break; - case 69421: - dalamudInterface?.ToggleSettingsWindow(); - break; - default: - this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); - break; - } - } + // this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; } - /// - /// Implements IDisposable. - /// - internal sealed partial class DalamudAtkTweaks : IDisposable + 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); + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(DalamudInterface dalamudInterface) { - private bool disposed = false; + this.hookAgentHudOpenSystemMenu.Enable(); + this.hookUiModuleRequestMainCommand.Enable(); + this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); + } - /// - /// Finalizes an instance of the class. - /// - ~DalamudAtkTweaks() => this.Dispose(false); + /* + private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args) + { + var systemText = Service.GetNullable()?.GetExcelSheet()?.GetRow(1059)?.Text?.RawString; // "System" + var interfaceManager = Service.GetNullable(); - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() + if (systemText == null || interfaceManager == null) + return; + + if (args.Title == systemText && this.configuration.DoButtonsSystemMenu && interfaceManager.IsDispatchingEvents) { - this.Dispose(true); - GC.SuppressFinalize(this); + var dalamudInterface = Service.Get(); + + args.Items.Insert(0, new CustomContextMenuItem(this.locDalamudSettings, selectedArgs => + { + dalamudInterface.ToggleSettingsWindow(); + })); + + args.Items.Insert(0, new CustomContextMenuItem(this.locDalamudPlugins, selectedArgs => + { + dalamudInterface.TogglePluginInstallerWindow(); + })); + } + } + */ + + 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 && this.configuration.IsFocusManagementEnabled) + { + 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 && this.configuration.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 interfaceManager = Service.GetNullable(); + if (interfaceManager == null) + { + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } - // this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; - } + if (!this.configuration.DoButtonsSystemMenu || !interfaceManager.IsDispatchingEvents) + { + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } - this.disposed = true; + // the max size (hardcoded) is 0x11/17, 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 >= 0x11) + { + 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 17 slots are the MainCommand + // the 17 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 + 17]; + this.atkValueChangeType(firstStringEntry, ValueType.String); + var secondStringEntry = &atkValueArgs[6 + 17]; + this.atkValueChangeType(secondStringEntry, ValueType.String); + + const int color = 539; + var strPlugins = new SeString().Append(new UIForegroundPayload(color)) + .Append($"{SeIconChar.BoxedLetterD.ToIconString()} ") + .Append(new UIForegroundPayload(0)) + .Append(this.locDalamudPlugins).Encode(); + var strSettings = new SeString().Append(new UIForegroundPayload(color)) + .Append($"{SeIconChar.BoxedLetterD.ToIconString()} ") + .Append(new UIForegroundPayload(0)) + .Append(this.locDalamudSettings).Encode(); + + // 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.GetNullable(); + + 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.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; + } + + this.disposed = true; + } +} diff --git a/Dalamud/Game/Libc/LibcFunction.cs b/Dalamud/Game/Libc/LibcFunction.cs index aa149a3ce..4c58376f2 100644 --- a/Dalamud/Game/Libc/LibcFunction.cs +++ b/Dalamud/Game/Libc/LibcFunction.cs @@ -5,74 +5,73 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class LibcFunction : IServiceType { - /// - /// This class handles creating cstrings utilizing native game methods. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class LibcFunction : IServiceType + private readonly LibcFunctionAddressResolver address; + private readonly StdStringFromCStringDelegate stdStringCtorCString; + private readonly StdStringDeallocateDelegate stdStringDeallocate; + + [ServiceManager.ServiceConstructor] + private LibcFunction(SigScanner sigScanner) { - private readonly LibcFunctionAddressResolver address; - private readonly StdStringFromCStringDelegate stdStringCtorCString; - private readonly StdStringDeallocateDelegate stdStringDeallocate; + this.address = new LibcFunctionAddressResolver(); + this.address.Setup(sigScanner); - [ServiceManager.ServiceConstructor] - private LibcFunction(SigScanner sigScanner) - { - this.address = new LibcFunctionAddressResolver(); - this.address.Setup(sigScanner); + 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 02b031b44..4189bc280 100644 --- a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs +++ b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs @@ -1,29 +1,28 @@ using System; -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 0b1d5375d..0fab9bf74 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -7,176 +7,175 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +public sealed class GameNetwork : IDisposable, IServiceType { - /// - /// This class handles interacting with game network events. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public sealed class GameNetwork : IDisposable, IServiceType + private readonly GameNetworkAddressResolver address; + private readonly Hook processZonePacketDownHook; + private readonly Hook processZonePacketUpHook; + private readonly Queue zoneInjectQueue = new(); + + private IntPtr baseAddress; + + [ServiceManager.ServiceConstructor] + private GameNetwork(SigScanner sigScanner) { - private readonly GameNetworkAddressResolver address; - private readonly Hook processZonePacketDownHook; - private readonly Hook processZonePacketUpHook; - private readonly Queue zoneInjectQueue = new(); + this.address = new GameNetworkAddressResolver(); + this.address.Setup(sigScanner); - 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}"); - [ServiceManager.ServiceConstructor] - private GameNetwork(SigScanner sigScanner) - { - this.address = new GameNetworkAddressResolver(); - this.address.Setup(sigScanner); - - 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 = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); - this.processZonePacketUpHook = Hook.FromAddress(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; - - /// - /// Dispose of managed and unmanaged resources. - /// - void IDisposable.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); - } - } - - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction() - { - this.processZonePacketDownHook.Enable(); - this.processZonePacketUpHook.Enable(); - } - - 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 = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); + this.processZonePacketUpHook = Hook.FromAddress(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; + + /// + /// Dispose of managed and unmanaged resources. + /// + void IDisposable.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); + } + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.processZonePacketDownHook.Enable(); + this.processZonePacketUpHook.Enable(); + } + + 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 565a1e2b9..5be85bd35 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -1,29 +1,28 @@ using System; -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 ff30a490c..72f54773d 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs @@ -1,52 +1,51 @@ 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; } - /// - /// Gets or sets Old Sharlayan's current tax rate. - /// - [JsonProperty("sharlayan")] - public uint Sharlayan { get; set; } - } + /// + /// Gets or sets Old Sharlayan's current tax rate. + /// + [JsonProperty("sharlayan")] + public uint Sharlayan { 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 55f005a5b..78634e608 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -10,189 +10,188 @@ 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.GetNullable(); + if (clientState == null) + return; - /// - /// 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.GetNullable(); - if (clientState == null) - return; - - 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.GetNullable(); - if (clientState == null) - return; + 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, - Sharlayan = taxRates.SharlayanTax, - }, - }; - - 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.GetNullable(); - if (clientState == null) - return; - - 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.GetNullable(); + if (clientState == null) + return; + + // ==================================================================================== + + 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, + Sharlayan = taxRates.SharlayanTax, + }, + }; + + 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.GetNullable(); + if (clientState == null) + return; + + 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 93c46f750..968d97682 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -16,280 +16,279 @@ 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. +/// +[ServiceManager.EarlyLoadedService] +internal class NetworkHandlers : IServiceType { - /// - /// This class handles network notifications and uploading market board data. - /// - [ServiceManager.EarlyLoadedService] - internal class NetworkHandlers : IServiceType + private readonly List marketBoardRequests = new(); + + private readonly IMarketBoardUploader uploader; + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration configuration = Service.Get(); + + private MarketBoardPurchaseHandler marketBoardPurchaseHandler; + + [ServiceManager.ServiceConstructor] + private NetworkHandlers(GameNetwork gameNetwork) { - private readonly List marketBoardRequests = new(); + this.uploader = new UniversalisMarketBoardUploader(); - private readonly IMarketBoardUploader uploader; + gameNetwork.NetworkMessage += this.OnNetworkMessage; + } - [ServiceManager.ServiceDependency] - private readonly DalamudConfiguration configuration = Service.Get(); + /// + /// Event which gets fired when a duty is ready. + /// + public event EventHandler CfPop; - private MarketBoardPurchaseHandler marketBoardPurchaseHandler; + private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) + { + var dataManager = Service.GetNullable(); - [ServiceManager.ServiceConstructor] - private NetworkHandlers(GameNetwork gameNetwork) + if (dataManager?.IsDataReady != true) + return; + + if (direction == NetworkMessageDirection.ZoneUp) { - this.uploader = new UniversalisMarketBoardUploader(); - - gameNetwork.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 != true) - return; - - if (direction == NetworkMessageDirection.ZoneUp) - { - if (this.configuration.IsMbCollect) - { - if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"]) - { - this.marketBoardPurchaseHandler = MarketBoardPurchaseHandler.Read(dataPtr); - return; - } - } - - return; - } - - if (opCode == dataManager.ServerOpCodes["CfNotifyPop"]) - { - this.HandleCfPop(dataPtr); - return; - } - if (this.configuration.IsMbCollect) { - 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); - - if (taxes.Category != 0xb0009) - return; - - Log.Verbose( - "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5} sh#{6}", - taxes.LimsaLominsaTax, - taxes.GridaniaTax, - taxes.UldahTax, - taxes.IshgardTax, - taxes.KuganeTax, - taxes.CrystariumTax, - taxes.SharlayanTax); - - 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.GetNullable(); - if (dataManager == null) - return; + 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 += 0x1B; - var conditionId = reader.ReadUInt16(); - - if (notifyType != 3) - return; - - var cfConditionSheet = dataManager.GetExcelSheet()!; - var cfCondition = cfConditionSheet.GetRow(conditionId); - - if (cfCondition == null) + if (this.configuration.IsMbCollect) + { + 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 (this.configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) - { - var flashInfo = new NativeFunctions.FlashWindowInfo + var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone); + + if (request == default) { - 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 (this.configuration.DutyFinderChatMessage) - { - Service.GetNullable()?.Print($"Duty pop: {cfcName}"); + Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}"); + return; } - this.CfPop?.InvokeSafely(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); + + if (taxes.Category != 0xb0009) + return; + + Log.Verbose( + "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5} sh#{6}", + taxes.LimsaLominsaTax, + taxes.GridaniaTax, + taxes.UldahTax, + taxes.IshgardTax, + taxes.KuganeTax, + taxes.CrystariumTax, + taxes.SharlayanTax); + + 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.GetNullable(); + if (dataManager == null) + return; + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64); + using var reader = new BinaryReader(stream); + + var notifyType = reader.ReadByte(); + stream.Position += 0x1B; + 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 (this.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 (this.configuration.DutyFinderChatMessage) + { + Service.GetNullable()?.Print($"Duty pop: {cfcName}"); + } + + this.CfPop?.InvokeSafely(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 d3334ae96..8439389ff 100644 --- a/Dalamud/Game/Network/Internal/WinSockHandlers.cs +++ b/Dalamud/Game/Network/Internal/WinSockHandlers.cs @@ -4,57 +4,56 @@ using System.Runtime.InteropServices; using Dalamud.Hooking; -namespace Dalamud.Game.Network.Internal +namespace Dalamud.Game.Network.Internal; + +/// +/// This class enables TCP optimizations in the game socket for better performance. +/// +[ServiceManager.EarlyLoadedService] +internal sealed class WinSockHandlers : IDisposable, IServiceType { - /// - /// This class enables TCP optimizations in the game socket for better performance. - /// - [ServiceManager.EarlyLoadedService] - internal sealed class WinSockHandlers : IDisposable, IServiceType + private Hook ws2SocketHook; + + [ServiceManager.ServiceConstructor] + private WinSockHandlers() { - private Hook ws2SocketHook; + this.ws2SocketHook = Hook.FromImport(null, "ws2_32.dll", "socket", 23, this.OnSocket); + this.ws2SocketHook?.Enable(); + } - [ServiceManager.ServiceConstructor] - private 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.FromImport(null, "ws2_32.dll", "socket", 23, this.OnSocket); - 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 2af4757b2..4e9b67a74 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. +/// +public class MarketBoardPurchase { - /// - /// Represents market board purchase information. This message is received from the - /// server when a purchase is made at a market board. - /// - public 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 75bbdf332..fdc7bd83a 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. +/// +public class MarketBoardPurchaseHandler { - /// - /// Represents market board purchase information. This message is sent from the - /// client when a purchase is made at a market board. - /// - public 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 0cb21e5d8..53ce41d44 100644 --- a/Dalamud/Game/Network/Structures/MarketTaxRates.cs +++ b/Dalamud/Game/Network/Structures/MarketTaxRates.cs @@ -1,81 +1,80 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures +namespace Dalamud.Game.Network.Structures; + +/// +/// This class represents the "Result Dialog" packet. This is also used e.g. for reduction results, but we only care about tax rates. +/// We can do that by checking the "Category" field. +/// +public class MarketTaxRates { - /// - /// This class represents the "Result Dialog" packet. This is also used e.g. for reduction results, but we only care about tax rates. - /// We can do that by checking the "Category" field. - /// - public class MarketTaxRates + private MarketTaxRates() { - private MarketTaxRates() - { - } + } - /// - /// Gets the category of this ResultDialog packet. - /// - public uint Category { get; private set; } + /// + /// Gets the category of this ResultDialog packet. + /// + public uint Category { get; private set; } - /// - /// 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; } - /// - /// Gets the tax rate in the Crystarium. - /// - public uint SharlayanTax { get; private set; } + /// + /// Gets the tax rate in the Crystarium. + /// + public uint SharlayanTax { 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(); - output.Category = reader.ReadUInt32(); - stream.Position += 4; - output.LimsaLominsaTax = reader.ReadUInt32(); - output.GridaniaTax = reader.ReadUInt32(); - output.UldahTax = reader.ReadUInt32(); - output.IshgardTax = reader.ReadUInt32(); - output.KuganeTax = reader.ReadUInt32(); - output.CrystariumTax = reader.ReadUInt32(); - output.SharlayanTax = reader.ReadUInt32(); + output.Category = reader.ReadUInt32(); + stream.Position += 4; + output.LimsaLominsaTax = reader.ReadUInt32(); + output.GridaniaTax = reader.ReadUInt32(); + output.UldahTax = reader.ReadUInt32(); + output.IshgardTax = reader.ReadUInt32(); + output.KuganeTax = reader.ReadUInt32(); + output.CrystariumTax = reader.ReadUInt32(); + output.SharlayanTax = reader.ReadUInt32(); - return output; - } + return output; } } diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 9f5a8e33c..197131c14 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -12,540 +12,539 @@ using Dalamud.IoC.Internal; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// A SigScanner facilitates searching for memory signatures in a given ProcessModule. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public class SigScanner : IDisposable, IServiceType { + private readonly FileInfo? cacheFile; + + private IntPtr moduleCopyPtr; + private long moduleCopyOffset; + + private ConcurrentDictionary? textCache; + /// - /// 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 class SigScanner : IDisposable, IServiceType + /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. + /// File used to cached signatures. + public SigScanner(bool doCopy = false, FileInfo? cacheFile = null) + : this(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile) { - private readonly FileInfo? cacheFile; + } - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; + /// + /// 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. + /// File used to cached signatures. + public SigScanner(ProcessModule module, bool doCopy = false, FileInfo? cacheFile = null) + { + this.cacheFile = cacheFile; + this.Module = module; + this.Is32BitProcess = !Environment.Is64BitProcess; + this.IsCopy = doCopy; - private ConcurrentDictionary? textCache; + // Limit the search space to .text section. + this.SetupSearchSpace(module); - /// - /// 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. - /// File used to cached signatures. - public SigScanner(bool doCopy = false, FileInfo? cacheFile = null) - : this(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile) + if (this.IsCopy) + this.SetupCopiedSegments(); + + Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}"); + Log.Verbose($"Module size: 0x{this.TextSectionSize:X}"); + + if (cacheFile != null) + this.Load(); + } + + /// + /// 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. - /// File used to cached signatures. - public SigScanner(ProcessModule module, bool doCopy = false, FileInfo? cacheFile = null) + catch (KeyNotFoundException) { - this.cacheFile = cacheFile; - 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}"); - - if (cacheFile != null) - this.Load(); + result = IntPtr.Zero; + return false; } + } - /// - /// Gets a value indicating whether or not the search on this module is performed on a copy. - /// - public bool IsCopy { get; } + /// + /// 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; - /// - /// 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) + do { - 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; + 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)); - /// - /// 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) + 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 { - 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) + { + if (this.textCache != null) + { + if (this.textCache.TryGetValue(signature, out var address)) { - result = Scan(baseAddress, size, signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; + return new IntPtr(address + this.Module.BaseAddress.ToInt64()); } } - /// - /// 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 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) + scanRet = ReadJmpCallSig(scanRet); + + // If this is below the module, there's bound to be a problem with the sig/resolution... Let's not save it + // TODO: THIS IS A HACK! FIX THE ROOT CAUSE! + if (this.textCache != null && scanRet.ToInt64() >= this.Module.BaseAddress.ToInt64()) { - 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); + this.textCache[signature] = scanRet.ToInt64() - this.Module.BaseAddress.ToInt64(); } - /// - /// 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) + 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 { - 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() + { + this.Save(); + Marshal.FreeHGlobal(this.moduleCopyPtr); + } + + /// + /// Save the current state of the cache. + /// + internal void Save() + { + if (this.cacheFile == null) + return; + + try + { + File.WriteAllText(this.cacheFile.FullName, JsonConvert.SerializeObject(this.textCache)); + Log.Information("Saved cache to {CachePath}", this.cacheFile); + } + catch (Exception e) + { + Log.Warning(e, "Failed to save cache to {CachePath}", this.cacheFile); + } + } + + /// + /// 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 == "**") { - result = this.GetStaticAddressFromSig(signature, offset); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; + needle[i] = 0; + mask[i] = true; + continue; } + + needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); + mask[i] = false; } - /// - /// Scan for a byte signature in the .data section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanData(string signature) + 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) { - 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 + int position; + for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) { - 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) - { - if (this.textCache != null) - { - if (this.textCache.TryGetValue(signature, out var address)) - { - return new IntPtr(address + this.Module.BaseAddress.ToInt64()); - } + if (position == 0) + return offset; } - 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) - scanRet = ReadJmpCallSig(scanRet); - - // If this is below the module, there's bound to be a problem with the sig/resolution... Let's not save it - // TODO: THIS IS A HACK! FIX THE ROOT CAUSE! - if (this.textCache != null && scanRet.ToInt64() >= this.Module.BaseAddress.ToInt64()) - { - this.textCache[signature] = scanRet.ToInt64() - this.Module.BaseAddress.ToInt64(); - } - - return scanRet; + offset += badShift[*(buffer + offset + last)]; } - /// - /// 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) + 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) { - 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() + 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++) { - this.Save(); - Marshal.FreeHGlobal(this.moduleCopyPtr); - } + var sectionName = Marshal.ReadInt64(sectionCursor); - /// - /// Save the current state of the cache. - /// - internal void Save() - { - if (this.cacheFile == null) - return; - - try - { - File.WriteAllText(this.cacheFile.FullName, JsonConvert.SerializeObject(this.textCache)); - Log.Information("Saved cache to {CachePath}", this.cacheFile); - } - catch (Exception e) - { - Log.Warning(e, "Failed to save cache to {CachePath}", this.cacheFile); - } - } - - /// - /// 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); + 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; + } - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); + 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(); + } + + private void Load() + { + if (this.cacheFile is not { Exists: true }) + { + this.textCache = new(); + return; } - private void Load() + try { - if (this.cacheFile is not { Exists: true }) - { - this.textCache = new(); - return; - } - - try - { - this.textCache = - JsonConvert.DeserializeObject>( - File.ReadAllText(this.cacheFile.FullName)) ?? new ConcurrentDictionary(); - } - catch (Exception ex) - { - this.textCache = new ConcurrentDictionary(); - Log.Error(ex, "Couldn't load cached sigs"); - } + this.textCache = + JsonConvert.DeserializeObject>( + File.ReadAllText(this.cacheFile.FullName)) ?? new ConcurrentDictionary(); + } + catch (Exception ex) + { + this.textCache = new ConcurrentDictionary(); + Log.Error(ex, "Couldn't load cached sigs"); } } } 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 b913b2ec9..c1be00613 100644 --- a/Dalamud/Game/Text/SeIconChar.cs +++ b/Dalamud/Game/Text/SeIconChar.cs @@ -1,748 +1,747 @@ -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 collectible icon unicode character. - /// - Collectible = 0xE03D, - - /// - /// 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 collectible icon unicode character. + /// + Collectible = 0xE03D, + + /// + /// 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/SeIconCharExtensions.cs b/Dalamud/Game/Text/SeIconCharExtensions.cs index 648b332c2..717b5e842 100644 --- a/Dalamud/Game/Text/SeIconCharExtensions.cs +++ b/Dalamud/Game/Text/SeIconCharExtensions.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.Text +namespace Dalamud.Game.Text; + +/// +/// Extension methods for . +/// +public static class SeIconCharExtensions { /// - /// Extension methods for . + /// Convert the SeIconChar to a type. /// - public static class SeIconCharExtensions + /// The icon to convert. + /// The converted icon. + public static char ToIconChar(this SeIconChar icon) { - /// - /// Convert the SeIconChar to a type. - /// - /// The icon to convert. - /// The converted icon. - public static char ToIconChar(this SeIconChar icon) - { - return (char)icon; - } + return (char)icon; + } - /// - /// Conver the SeIconChar to a type. - /// - /// The icon to convert. - /// The converted icon. - public static string ToIconString(this SeIconChar icon) - { - return string.Empty + (char)icon; - } + /// + /// Conver the SeIconChar to a type. + /// + /// The icon to convert. + /// The converted icon. + public static string ToIconString(this SeIconChar icon) + { + return string.Empty + (char)icon; } } diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index 3209d1496..64af2a2a9 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -1,463 +1,462 @@ -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, - - /// - /// The Fan Festival logo. - /// - FanFestival = 112, - - /// - /// The Sharlayan region icon. - /// - Sharlayan = 113, - - /// - /// The Ilsabard region icon. - /// - Ilsabard = 114, - - /// - /// The Garlemald region icon. - /// - Garlemald = 115, - - /// - /// The Island Sanctuary icon. - /// - IslandSanctuary = 116, - } + 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, + + /// + /// The Fan Festival logo. + /// + FanFestival = 112, + + /// + /// The Sharlayan region icon. + /// + Sharlayan = 113, + + /// + /// The Ilsabard region icon. + /// + Ilsabard = 114, + + /// + /// The Garlemald region icon. + /// + Garlemald = 115, + + /// + /// The Island Sanctuary icon. + /// + IslandSanctuary = 116, } 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 9f8b4e9ff..dc4b7cba0 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -16,406 +16,405 @@ 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 + [JsonIgnore] + 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. - /// - [JsonIgnore] - 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 preferred.")] - 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 preferred.")] - 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 preferred.")] + 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 preferred.")] + 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..7b8ffd146 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; - } - - /// - /// Initializes a new instance of the class. - /// - internal AutoTranslatePayload() - { - } - - /// - public override PayloadType Type => PayloadType.AutoTranslateText; - - /// - /// 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() - { - START_BYTE, - (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, - (byte)this.group, - }; - bytes.AddRange(keyBytes); - bytes.Add(END_BYTE); - - return bytes.ToArray(); - } - - /// - 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 - { - // 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 - - 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 - { - // 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]; - - 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}"); - } - } - - return value; + // 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() + { + START_BYTE, + (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, + (byte)this.group, + }; + bytes.AddRange(keyBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + /// + 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 + { + // 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 + + 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 + { + // 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]; + + 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}"); + } + } + + 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 0a61f5ef3..37fb863a5 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs @@ -1,81 +1,80 @@ 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; + } + + /// + /// 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 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; } + + /// + public override PayloadType Type => PayloadType.EmphasisItalic; + + /// + 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); + + var chunkLen = enabledBytes.Length + 1; + var bytes = new List() { - this.IsEnabled = enabled; - } + START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen, + }; + bytes.AddRange(enabledBytes); + bytes.Add(END_BYTE); - /// - /// Initializes a new instance of the class. - /// Creates an EmphasisItalicPayload. - /// - internal EmphasisItalicPayload() - { - } + return bytes.ToArray(); + } - /// - /// 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 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 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); - - var chunkLen = enabledBytes.Length + 1; - var bytes = new List() - { - START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen, - }; - bytes.AddRange(enabledBytes); - bytes.Add(END_BYTE); - - 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..7963d422f 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; - } + START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen, + }); + bytes.AddRange(indexBytes); + bytes.Add(END_BYTE); + return bytes.ToArray(); + } - /// - /// 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(); - } - - /// - 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 06e4dfc76..e3415b2dc 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -7,278 +7,277 @@ using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using Serilog; -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; + + [JsonProperty] + private uint rawItemId; + /// - /// 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.rawItemId = itemId; + if (isHq) + this.rawItemId += (uint)ItemKind.Hq; - // 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; + this.Kind = isHq ? ItemKind.Hq : ItemKind.Normal; + this.displayName = displayNameOverride; + } - [JsonProperty] - private uint rawItemId; + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + /// The id of the item. + /// Kind of item to encode. + /// 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, ItemKind kind = ItemKind.Normal, string? displayNameOverride = null) + { + this.Kind = kind; + this.rawItemId = itemId; + if (kind != ItemKind.EventItem) + this.rawItemId += (uint)kind; + + this.displayName = displayNameOverride; + } + + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + internal ItemPayload() + { + } + + /// + /// Kinds of items that can be fetched from this payload. + /// + public enum ItemKind : uint + { + /// + /// Normal items. + /// + Normal, /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. + /// Collectible Items. /// - /// 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) - { - this.rawItemId = itemId; - if (isHq) - this.rawItemId += (uint)ItemKind.Hq; + Collectible = 500_000, - this.Kind = isHq ? ItemKind.Hq : ItemKind.Normal; - this.displayName = displayNameOverride; + /// + /// High-Quality items. + /// + Hq = 1_000_000, + + /// + /// Event/Key items. + /// + EventItem = 2_000_000, + } + + /// + public override PayloadType Type => PayloadType.Item; + + /// + /// 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 + { + return this.displayName; } - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. - /// - /// The id of the item. - /// Kind of item to encode. - /// 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, ItemKind kind = ItemKind.Normal, string? displayNameOverride = null) + set { - this.Kind = kind; - this.rawItemId = itemId; - if (kind != ItemKind.EventItem) - this.rawItemId += (uint)kind; - - this.displayName = displayNameOverride; - } - - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. - /// - internal ItemPayload() - { - } - - /// - /// Kinds of items that can be fetched from this payload. - /// - public enum ItemKind : uint - { - /// - /// Normal items. - /// - Normal, - - /// - /// Collectible Items. - /// - Collectible = 500_000, - - /// - /// High-Quality items. - /// - Hq = 1_000_000, - - /// - /// Event/Key items. - /// - EventItem = 2_000_000, - } - - /// - public override PayloadType Type => PayloadType.Item; - - /// - /// 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 - { - return this.displayName; - } - - set - { - this.displayName = value; - this.Dirty = true; - } - } - - /// - /// Gets the actual item ID of this payload. - /// - [JsonIgnore] - public uint ItemId => GetAdjustedId(this.rawItemId).ItemId; - - /// - /// Gets the raw, unadjusted item ID of this payload. - /// - [JsonIgnore] - public uint RawItemId => this.rawItemId; - - /// - /// Gets the underlying Lumina Item represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Item? Item - { - get - { - // TODO(goat): This should be revamped/removed on an API level change. - if (this.Kind == ItemKind.EventItem) - { - Log.Warning("Event items cannot be fetched from the ItemPayload"); - return null; - } - - this.item ??= this.DataResolver.GetExcelSheet()!.GetRow(this.ItemId); - return this.item; - } - } - - /// - /// Gets a value indicating whether or not this item link is for a high-quality version of the item. - /// - [JsonProperty] - public bool IsHQ => this.Kind == ItemKind.Hq; - - /// - /// Gets or sets the kind of item represented by this payload. - /// - [JsonProperty] - public ItemKind Kind { get; set; } = ItemKind.Normal; - - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. - /// - /// The raw, unadjusted id 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. - /// The created item payload. - public static ItemPayload FromRaw(uint rawItemId, string? displayNameOverride = null) - { - var (id, kind) = GetAdjustedId(rawItemId); - return new ItemPayload(id, kind, displayNameOverride); - } - - /// - public override string ToString() - { - return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}"; - } - - /// - protected override byte[] EncodeImpl() - { - var idBytes = MakeInteger(this.rawItemId); - 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() - { - START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink, - }; - 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) - { - var nameLen = this.displayName.Length + 1; - if (this.IsHQ) - { - nameLen += 4; // space plus 3 bytes for HQ symbol - } - - bytes.AddRange(new byte[] - { - 0xFF, // unk - (byte)nameLen, - }); - bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); - - 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) - { - this.rawItemId = GetInteger(reader); - this.Kind = GetAdjustedId(this.rawItemId).Kind; - - 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); - } - } - - private static (uint ItemId, ItemKind Kind) GetAdjustedId(uint rawItemId) - { - return rawItemId switch - { - > 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible), - > 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq), - > 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted - _ => (rawItemId, ItemKind.Normal), - }; + this.displayName = value; + this.Dirty = true; } } + + /// + /// Gets the actual item ID of this payload. + /// + [JsonIgnore] + public uint ItemId => GetAdjustedId(this.rawItemId).ItemId; + + /// + /// Gets the raw, unadjusted item ID of this payload. + /// + [JsonIgnore] + public uint RawItemId => this.rawItemId; + + /// + /// Gets the underlying Lumina Item represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Item? Item + { + get + { + // TODO(goat): This should be revamped/removed on an API level change. + if (this.Kind == ItemKind.EventItem) + { + Log.Warning("Event items cannot be fetched from the ItemPayload"); + return null; + } + + this.item ??= this.DataResolver.GetExcelSheet()!.GetRow(this.ItemId); + return this.item; + } + } + + /// + /// Gets a value indicating whether or not this item link is for a high-quality version of the item. + /// + [JsonProperty] + public bool IsHQ => this.Kind == ItemKind.Hq; + + /// + /// Gets or sets the kind of item represented by this payload. + /// + [JsonProperty] + public ItemKind Kind { get; set; } = ItemKind.Normal; + + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + /// The raw, unadjusted id 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. + /// The created item payload. + public static ItemPayload FromRaw(uint rawItemId, string? displayNameOverride = null) + { + var (id, kind) = GetAdjustedId(rawItemId); + return new ItemPayload(id, kind, displayNameOverride); + } + + /// + public override string ToString() + { + return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}"; + } + + /// + protected override byte[] EncodeImpl() + { + var idBytes = MakeInteger(this.rawItemId); + 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() + { + START_BYTE, + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink, + }; + 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) + { + var nameLen = this.displayName.Length + 1; + if (this.IsHQ) + { + nameLen += 4; // space plus 3 bytes for HQ symbol + } + + bytes.AddRange(new byte[] + { + 0xFF, // unk + (byte)nameLen, + }); + bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); + + 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) + { + this.rawItemId = GetInteger(reader); + this.Kind = GetAdjustedId(this.rawItemId).Kind; + + 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); + } + } + + private static (uint ItemId, ItemKind Kind) GetAdjustedId(uint rawItemId) + { + return rawItemId switch + { + > 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible), + > 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq), + > 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted + _ => (rawItemId, ItemKind.Normal), + }; + } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs index 24b6e8ebe..50945a7ce 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs @@ -5,244 +5,243 @@ 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; - - [JsonProperty] - private uint territoryTypeId; - - [JsonProperty] - private uint mapId; - - /// - /// 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) - { - 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.Map.OffsetX); - this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetY); - } - - /// - /// 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; - } - - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a human-readable position. - /// - internal MapLinkPayload() - { - } - - /// - 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, this.Map.OffsetX); - - /// - /// 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, this.Map.OffsetY); - - // 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() - { - START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink, - }; - - bytes.AddRange(packedTerritoryAndMapBytes); - bytes.AddRange(xBytes); - bytes.AddRange(yBytes); - - // 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 - // from https://github.com/xivapi/xivapi-mappy/blob/master/Mappy/Helpers/MapHelper.cs - // the raw scale from the map needs to be scaled down by a factor of 100 - // the raw pos also needs to be scaled down by a factor of 1000 - // the tile scale is ~50, but is exactly 2048/41, more info in the md file - private float ConvertRawPositionToMapCoordinate(int pos, float scale, short offset) - { - // extra 1/1000 because that is how the network ints are done - const float networkAdjustment = 1f; - - // scaling - var trueScale = scale / 100f; - var truePos = pos / 1000f; - - var computedPos = (truePos + offset) * trueScale; - // pretty weird formula, but obviously has something to do with the tile scaling - return (41f / trueScale * ((computedPos + 1024f) / 2048f)) + networkAdjustment; - } - - // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that - private int ConvertMapCoordinateToRawPosition(float pos, float scale, short offset) - { - const float networkAdjustment = 1f; - - // scaling - var trueScale = scale / 100f; - - var num2 = (((pos - networkAdjustment) * trueScale / 41f * 2048f) - 1024f) / trueScale; - // (pos - offset) / scale, with the scaling on num2 done before for precision - num2 *= 1000f; - return (int)num2 - (offset * 1000); - } - - #endregion + 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.Map.OffsetX); + this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetY); } + + /// + /// 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; + } + + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a human-readable position. + /// + internal MapLinkPayload() + { + } + + /// + 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, this.Map.OffsetX); + + /// + /// 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, this.Map.OffsetY); + + // 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() + { + START_BYTE, + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink, + }; + + bytes.AddRange(packedTerritoryAndMapBytes); + bytes.AddRange(xBytes); + bytes.AddRange(yBytes); + + // 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 + // from https://github.com/xivapi/xivapi-mappy/blob/master/Mappy/Helpers/MapHelper.cs + // the raw scale from the map needs to be scaled down by a factor of 100 + // the raw pos also needs to be scaled down by a factor of 1000 + // the tile scale is ~50, but is exactly 2048/41, more info in the md file + private float ConvertRawPositionToMapCoordinate(int pos, float scale, short offset) + { + // extra 1/1000 because that is how the network ints are done + const float networkAdjustment = 1f; + + // scaling + var trueScale = scale / 100f; + var truePos = pos / 1000f; + + var computedPos = (truePos + offset) * trueScale; + // pretty weird formula, but obviously has something to do with the tile scaling + return (41f / trueScale * ((computedPos + 1024f) / 2048f)) + networkAdjustment; + } + + // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that + private int ConvertMapCoordinateToRawPosition(float pos, float scale, short offset) + { + const float networkAdjustment = 1f; + + // scaling + var trueScale = scale / 100f; + + var num2 = (((pos - networkAdjustment) * trueScale / 41f * 2048f) - 1024f) / trueScale; + // (pos - offset) / scale, with the scaling on num2 done before for precision + num2 *= 1000f; + return (int)num2 - (offset * 1000); + } + + #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..b9d6bc896 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -5,132 +5,131 @@ 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 { - } - - /// - /// 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 or sets the player's displayed name. This does not contain the server name. - /// - [JsonIgnore] - public string PlayerName - { - get - { - return this.playerName; - } - - set - { - this.playerName = value; - this.Dirty = true; - } - } - - /// - /// 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() - { - START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, - /* unk */ 0x01, - (byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually - /* unk */ 0x01, - /* unk */ 0xFF, // these sometimes vary but are frequently this - (byte)(this.playerName.Length + 1), - }; - - bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); - bytes.Add(END_BYTE); - - // 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()); - - // 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(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // unk - reader.ReadByte(); - - this.serverId = GetInteger(reader); - - // unk - reader.ReadBytes(2); - - var nameLen = (int)GetInteger(reader); - this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); + this.playerName = value; + this.Dirty = true; } } + + /// + /// 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() + { + START_BYTE, + (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, + /* unk */ 0x01, + (byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually + /* unk */ 0x01, + /* unk */ 0xFF, // these sometimes vary but are frequently this + (byte)(this.playerName.Length + 1), + }; + + bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); + bytes.Add(END_BYTE); + + // 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()); + + // 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(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // unk + reader.ReadByte(); + + this.serverId = GetInteger(reader); + + // unk + reader.ReadBytes(2); + + 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..e5b9e635e 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) + /// + public override PayloadType Type => PayloadType.Quest; + + /// + /// 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 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() { - this.questId = questId; - } + START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.QuestLink, + }; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable quest link for the specified quest. - /// - internal QuestPayload() - { - } + bytes.AddRange(idBytes); + bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE }); + return bytes.ToArray(); + } - /// - public override PayloadType Type => PayloadType.Quest; - - /// - /// 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 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() - { - START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.QuestLink, - }; - - 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..c42805b92 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(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The chunk type. - [JsonConstructor] - internal RawPayload(byte chunkType) - { - this.chunkType = chunkType; - } - - /// - /// 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 - { - // 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(); - } - - 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() - { - START_BYTE, - this.chunkType, - (byte)chunkLen, - }; - bytes.AddRange(this.data); - - bytes.Add(END_BYTE); - - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); + // 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(); + } + + 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() + { + START_BYTE, + this.chunkType, + (byte)chunkLen, + }; + bytes.AddRange(this.data); + + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + /// + 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..3e10f7659 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) + /// + public override PayloadType Type => PayloadType.Status; + + /// + /// 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 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() { - this.statusId = statusId; - } + START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status, + }; - /// - /// Initializes a new instance of the class. - /// Creates a new StatusPayload for the given status id. - /// - internal StatusPayload() - { - } + bytes.AddRange(idBytes); + // unk + bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE }); - /// - public override PayloadType Type => PayloadType.Status; + return bytes.ToArray(); + } - /// - /// 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 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() - { - 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 }); - - 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 8242f8b3f..1cb27f4ea 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs @@ -5,95 +5,94 @@ 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; + + set { - this.text = text; + 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(); } - /// - /// Initializes a new instance of the class. - /// Creates a new TextPayload for the given text. - /// - internal TextPayload() + return Encoding.UTF8.GetBytes(this.text); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + var textBytes = new List(); + + while (reader.BaseStream.Position < endOfStream) { - } - - /// - 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; - - set + var nextByte = reader.ReadByte(); + if (nextByte == START_BYTE) { - 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(); + // rewind since this byte isn't part of this payload + reader.BaseStream.Position--; + break; } - return Encoding.UTF8.GetBytes(this.text); + textBytes.Add(nextByte); } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) + if (textBytes.Count > 0) { - 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..0586089f8 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..0d9081c08 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 012839c00..412a78c3f 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -10,397 +10,396 @@ 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(); + } + + /// + /// 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); + } + + /// + /// 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 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 + { + get { - 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. - 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[] - { - 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 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 - { - 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) - { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); - } - } - - return new SeString(payloads); - } - - /// - /// 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) - { - 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) => - CreateItemLink(itemId, isHq ? ItemPayload.ItemKind.Hq : ItemPayload.ItemKind.Normal, displayNameOverride); - - /// - /// 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. - /// The kind of item to link. - /// An optional name override to display, instead of the actual item name. - /// An SeString containing all the payloads necessary to display an item link in the chat log. - public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null) - { - var data = Service.Get(); - - var displayName = displayNameOverride; - if (displayName == null) - { - switch (kind) - { - case ItemPayload.ItemKind.Normal: - case ItemPayload.ItemKind.Collectible: - case ItemPayload.ItemKind.Hq: - displayName = data.GetExcelSheet()?.GetRow(itemId)?.Name; - break; - case ItemPayload.ItemKind.EventItem: - displayName = data.GetExcelSheet()?.GetRow(itemId)?.Name; - break; - default: - throw new ArgumentOutOfRangeException(nameof(kind), kind, null); - } - } - - if (displayName == null) - { - throw new Exception("Invalid item ID specified, could not determine item name."); - } - - if (kind == ItemPayload.ItemKind.Hq) - { - displayName += $" {(char)SeIconChar.HighQuality}"; - } - else if (kind == ItemPayload.ItemKind.Collectible) - { - displayName += $" {(char)SeIconChar.Collectible}"; - } - - // 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, kind), - // 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); - - 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) - { - 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); - - 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) - { - 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); - - 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) - { - 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) - { - 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) - { - var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - TypeNameHandling = TypeNameHandling.Auto, - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, - }); - - return s; - } - - /// - /// Serializes the SeString to json. - /// - /// An json representation of this object. - public string ToJson() - { - return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() - { - 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) - { - 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) - { - messageBytes.AddRange(p.Encode()); - } - - return messageBytes.ToArray(); - } - - /// - /// Get the text value of this SeString. - /// - /// The TextValue property. - public override string ToString() - { - return this.TextValue; + 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) + { + var payload = Payload.Decode(reader); + if (payload != null) + payloads.Add(payload); + } + } + + return new SeString(payloads); + } + + /// + /// 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) + { + 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) => + CreateItemLink(itemId, isHq ? ItemPayload.ItemKind.Hq : ItemPayload.ItemKind.Normal, displayNameOverride); + + /// + /// 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. + /// The kind of item to link. + /// An optional name override to display, instead of the actual item name. + /// An SeString containing all the payloads necessary to display an item link in the chat log. + public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null) + { + var data = Service.Get(); + + var displayName = displayNameOverride; + if (displayName == null) + { + switch (kind) + { + case ItemPayload.ItemKind.Normal: + case ItemPayload.ItemKind.Collectible: + case ItemPayload.ItemKind.Hq: + displayName = data.GetExcelSheet()?.GetRow(itemId)?.Name; + break; + case ItemPayload.ItemKind.EventItem: + displayName = data.GetExcelSheet()?.GetRow(itemId)?.Name; + break; + default: + throw new ArgumentOutOfRangeException(nameof(kind), kind, null); + } + } + + if (displayName == null) + { + throw new Exception("Invalid item ID specified, could not determine item name."); + } + + if (kind == ItemPayload.ItemKind.Hq) + { + displayName += $" {(char)SeIconChar.HighQuality}"; + } + else if (kind == ItemPayload.ItemKind.Collectible) + { + displayName += $" {(char)SeIconChar.Collectible}"; + } + + // 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, kind), + // 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); + + 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) + { + 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); + + 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) + { + 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); + + 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) + { + 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) + { + 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) + { + var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + TypeNameHandling = TypeNameHandling.Auto, + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, + }); + + return s; + } + + /// + /// Serializes the SeString to json. + /// + /// An json representation of this object. + public string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() + { + 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) + { + 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) + { + messageBytes.AddRange(p.Encode()); + } + + return messageBytes.ToArray(); + } + + /// + /// Get the text value of this SeString. + /// + /// The TextValue property. + public override string ToString() + { + return this.TextValue; + } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs b/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs index a1401594d..b32741005 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs @@ -1,218 +1,217 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// Helper class to build SeStrings using a builder pattern. +/// +public class SeStringBuilder { /// - /// Helper class to build SeStrings using a builder pattern. + /// Gets the built SeString. /// - public class SeStringBuilder + public SeString BuiltString { get; init; } = new SeString(); + + /// + /// Append another SeString to the builder. + /// + /// The SeString to append. + /// The current builder. + public SeStringBuilder Append(SeString toAppend) { - /// - /// Gets the built SeString. - /// - public SeString BuiltString { get; init; } = new SeString(); - - /// - /// Append another SeString to the builder. - /// - /// The SeString to append. - /// The current builder. - public SeStringBuilder Append(SeString toAppend) - { - this.BuiltString.Append(toAppend); - return this; - } - - /// - /// Append raw text to the builder. - /// - /// The raw text. - /// The current builder. - public SeStringBuilder Append(string text) => this.AddText(text); - - /// - /// Append raw text to the builder. - /// - /// The raw text. - /// The current builder. - public SeStringBuilder AddText(string text) => this.Add(new TextPayload(text)); - - /// - /// Start colored text in the current builder. - /// - /// The text color. - /// The current builder. - public SeStringBuilder AddUiForeground(ushort colorKey) => this.Add(new UIForegroundPayload(colorKey)); - - /// - /// Turn off a previous colored text. - /// - /// The current builder. - public SeStringBuilder AddUiForegroundOff() => this.Add(UIForegroundPayload.UIForegroundOff); - - /// - /// Add colored text to the current builder. - /// - /// The raw text. - /// The text color. - /// The current builder. - public SeStringBuilder AddUiForeground(string text, ushort colorKey) - { - this.AddUiForeground(colorKey); - this.AddText(text); - return this.AddUiForegroundOff(); - } - - /// - /// Start an UiGlow in the current builder. - /// - /// The glow color. - /// The current builder. - public SeStringBuilder AddUiGlow(ushort colorKey) => this.Add(new UIGlowPayload(colorKey)); - - /// - /// Turn off a previous UiGlow. - /// - /// The current builder. - public SeStringBuilder AddUiGlowOff() => this.Add(UIGlowPayload.UIGlowOff); - - /// - /// Add glowing text to the current builder. - /// - /// The raw text. - /// The glow color. - /// The current builder. - public SeStringBuilder AddUiGlow(string text, ushort colorKey) - { - this.AddUiGlow(colorKey); - this.AddText(text); - return this.AddUiGlowOff(); - } - - /// - /// Add an icon to the builder. - /// - /// The icon to add. - /// The current builder. - public SeStringBuilder AddIcon(BitmapFontIcon icon) => this.Add(new IconPayload(icon)); - - /// - /// Add an item link to the builder. - /// - /// The item ID. - /// Whether or not the item is high quality. - /// Override for the item's name. - /// The current builder. - public SeStringBuilder AddItemLink(uint itemId, bool isHq, string? itemNameOverride = null) => - this.Add(new ItemPayload(itemId, isHq, itemNameOverride)); - - /// - /// Add an item link to the builder. - /// - /// The item ID. - /// Kind of item to encode. - /// Override for the item's name. - /// The current builder. - public SeStringBuilder AddItemLink(uint itemId, ItemPayload.ItemKind kind, string? itemNameOverride = null) => - this.Add(new ItemPayload(itemId, kind, itemNameOverride)); - - /// - /// Add an item link to the builder. - /// - /// The raw item ID. - /// The current builder. - public SeStringBuilder AddItemLinkRaw(uint rawItemId) => - this.Add(ItemPayload.FromRaw(rawItemId)); - - /// - /// Add italicized raw text to the builder. - /// - /// The raw text. - /// The current builder. - public SeStringBuilder AddItalics(string text) - { - this.Add(EmphasisItalicPayload.ItalicsOn); - this.AddText(text); - return this.Add(EmphasisItalicPayload.ItalicsOff); - } - - /// - /// Turn italics on. - /// - /// The current builder. - public SeStringBuilder AddItalicsOn() => this.Add(EmphasisItalicPayload.ItalicsOn); - - /// - /// Turn italics off. - /// - /// The current builder. - public SeStringBuilder AddItalicsOff() => this.Add(EmphasisItalicPayload.ItalicsOff); - - /// - /// Add a map link payload to the builder. - /// - /// 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. - /// The current builder. - public SeStringBuilder AddMapLink(uint territoryTypeId, uint mapId, int rawX, int rawY) => - this.Add(new MapLinkPayload(territoryTypeId, mapId, rawX, rawY)); - - /// - /// Add a map link payload to the builder. - /// - /// 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. - /// The current builder. - public SeStringBuilder AddMapLink( - uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) => - this.Add(new MapLinkPayload(territoryTypeId, mapId, niceXCoord, niceYCoord, fudgeFactor)); - - /// - /// Add a quest link to the builder. - /// - /// The quest ID. - /// The current builder. - public SeStringBuilder AddQuestLink(uint questId) => this.Add(new QuestPayload(questId)); - - /// - /// Add a status effect link to the builder. - /// - /// The status effect ID. - /// The current builder. - public SeStringBuilder AddStatusLink(uint statusId) => this.Add(new StatusPayload(statusId)); - - /// - /// Add a payload to the builder. - /// - /// The payload to add. - /// The current builder. - public SeStringBuilder Add(Payload payload) - { - this.BuiltString.Payloads.Add(payload); - return this; - } - - /// - /// Return the built string. - /// - /// The built string. - public SeString Build() => this.BuiltString; - - /// - /// Encode the built string to bytes. - /// - /// The built string, encoded to UTF-8 bytes. - public byte[] Encode() => this.BuiltString.Encode(); - - /// - /// Return the text representation of this string. - /// - /// The text representation of this string. - public override string ToString() => this.BuiltString.ToString(); + this.BuiltString.Append(toAppend); + return this; } + + /// + /// Append raw text to the builder. + /// + /// The raw text. + /// The current builder. + public SeStringBuilder Append(string text) => this.AddText(text); + + /// + /// Append raw text to the builder. + /// + /// The raw text. + /// The current builder. + public SeStringBuilder AddText(string text) => this.Add(new TextPayload(text)); + + /// + /// Start colored text in the current builder. + /// + /// The text color. + /// The current builder. + public SeStringBuilder AddUiForeground(ushort colorKey) => this.Add(new UIForegroundPayload(colorKey)); + + /// + /// Turn off a previous colored text. + /// + /// The current builder. + public SeStringBuilder AddUiForegroundOff() => this.Add(UIForegroundPayload.UIForegroundOff); + + /// + /// Add colored text to the current builder. + /// + /// The raw text. + /// The text color. + /// The current builder. + public SeStringBuilder AddUiForeground(string text, ushort colorKey) + { + this.AddUiForeground(colorKey); + this.AddText(text); + return this.AddUiForegroundOff(); + } + + /// + /// Start an UiGlow in the current builder. + /// + /// The glow color. + /// The current builder. + public SeStringBuilder AddUiGlow(ushort colorKey) => this.Add(new UIGlowPayload(colorKey)); + + /// + /// Turn off a previous UiGlow. + /// + /// The current builder. + public SeStringBuilder AddUiGlowOff() => this.Add(UIGlowPayload.UIGlowOff); + + /// + /// Add glowing text to the current builder. + /// + /// The raw text. + /// The glow color. + /// The current builder. + public SeStringBuilder AddUiGlow(string text, ushort colorKey) + { + this.AddUiGlow(colorKey); + this.AddText(text); + return this.AddUiGlowOff(); + } + + /// + /// Add an icon to the builder. + /// + /// The icon to add. + /// The current builder. + public SeStringBuilder AddIcon(BitmapFontIcon icon) => this.Add(new IconPayload(icon)); + + /// + /// Add an item link to the builder. + /// + /// The item ID. + /// Whether or not the item is high quality. + /// Override for the item's name. + /// The current builder. + public SeStringBuilder AddItemLink(uint itemId, bool isHq, string? itemNameOverride = null) => + this.Add(new ItemPayload(itemId, isHq, itemNameOverride)); + + /// + /// Add an item link to the builder. + /// + /// The item ID. + /// Kind of item to encode. + /// Override for the item's name. + /// The current builder. + public SeStringBuilder AddItemLink(uint itemId, ItemPayload.ItemKind kind, string? itemNameOverride = null) => + this.Add(new ItemPayload(itemId, kind, itemNameOverride)); + + /// + /// Add an item link to the builder. + /// + /// The raw item ID. + /// The current builder. + public SeStringBuilder AddItemLinkRaw(uint rawItemId) => + this.Add(ItemPayload.FromRaw(rawItemId)); + + /// + /// Add italicized raw text to the builder. + /// + /// The raw text. + /// The current builder. + public SeStringBuilder AddItalics(string text) + { + this.Add(EmphasisItalicPayload.ItalicsOn); + this.AddText(text); + return this.Add(EmphasisItalicPayload.ItalicsOff); + } + + /// + /// Turn italics on. + /// + /// The current builder. + public SeStringBuilder AddItalicsOn() => this.Add(EmphasisItalicPayload.ItalicsOn); + + /// + /// Turn italics off. + /// + /// The current builder. + public SeStringBuilder AddItalicsOff() => this.Add(EmphasisItalicPayload.ItalicsOff); + + /// + /// Add a map link payload to the builder. + /// + /// 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. + /// The current builder. + public SeStringBuilder AddMapLink(uint territoryTypeId, uint mapId, int rawX, int rawY) => + this.Add(new MapLinkPayload(territoryTypeId, mapId, rawX, rawY)); + + /// + /// Add a map link payload to the builder. + /// + /// 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. + /// The current builder. + public SeStringBuilder AddMapLink( + uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) => + this.Add(new MapLinkPayload(territoryTypeId, mapId, niceXCoord, niceYCoord, fudgeFactor)); + + /// + /// Add a quest link to the builder. + /// + /// The quest ID. + /// The current builder. + public SeStringBuilder AddQuestLink(uint questId) => this.Add(new QuestPayload(questId)); + + /// + /// Add a status effect link to the builder. + /// + /// The status effect ID. + /// The current builder. + public SeStringBuilder AddStatusLink(uint statusId) => this.Add(new StatusPayload(statusId)); + + /// + /// Add a payload to the builder. + /// + /// The payload to add. + /// The current builder. + public SeStringBuilder Add(Payload payload) + { + this.BuiltString.Payloads.Add(payload); + return this; + } + + /// + /// Return the built string. + /// + /// The built string. + public SeString Build() => this.BuiltString; + + /// + /// Encode the built string to bytes. + /// + /// The built string, encoded to UTF-8 bytes. + public byte[] Encode() => this.BuiltString.Encode(); + + /// + /// Return the text representation of this string. + /// + /// The text representation of this string. + public override string ToString() => this.BuiltString.ToString(); } diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs index 02c187bad..f0b38d429 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs @@ -5,108 +5,107 @@ 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")] +[ServiceManager.BlockingEarlyLoadedService] +[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] +public sealed class SeStringManager : IServiceType { - /// - /// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - [Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] - public sealed class SeStringManager : IServiceType + [ServiceManager.ServiceConstructor] + private SeStringManager() { - [ServiceManager.ServiceConstructor] - private 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 0d8081f62..be9fb8e91 100644 --- a/Dalamud/Game/Text/XivChatType.cs +++ b/Dalamud/Game/Text/XivChatType.cs @@ -1,247 +1,246 @@ -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 NPC Dialogue chat type. - /// - NPCDialogue = 61, + /// + /// The NPC Dialogue chat type. + /// + NPCDialogue = 61, - /// - /// The NPC Dialogue (Announcements) chat type. - /// - NPCDialogueAnnouncements = 68, + /// + /// The NPC Dialogue (Announcements) chat type. + /// + NPCDialogueAnnouncements = 68, - /// - /// 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 e12d7f4f8..4c551db04 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.TryAdd(Guid.NewGuid(), 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.TryAdd(Guid.NewGuid(), 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.TryAdd(Guid.NewGuid(), 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.TryAdd(Guid.NewGuid(), 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 8c12d5563..6e1fa24f2 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -6,405 +6,404 @@ using System.Runtime.InteropServices; using Dalamud.Configuration.Internal; using Dalamud.Hooking.Internal; -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 class Hook : IDisposable, IDalamudHook where T : Delegate { - /// - /// 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 class Hook : IDisposable, IDalamudHook where T : Delegate - { #pragma warning disable SA1310 - // ReSharper disable once InconsistentNaming - private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000; - // ReSharper disable once InconsistentNaming - private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000; + // ReSharper disable once InconsistentNaming + private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000; + // ReSharper disable once InconsistentNaming + private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000; #pragma warning restore SA1310 - private readonly IntPtr address; + private readonly IntPtr address; - private readonly Hook? compatHookImpl; + private readonly Hook? compatHookImpl; - /// - /// 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. - [Obsolete("Use Hook.FromAddress instead.")] - 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. + /// + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + [Obsolete("Use Hook.FromAddress instead.")] + 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. + [Obsolete("Use Hook.FromAddress instead.")] + public Hook(IntPtr address, T detour, bool useMinHook) + : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A memory address to install a hook. + internal Hook(IntPtr address) + { + this.address = address; + } + + [Obsolete("Use Hook.FromAddress instead.")] + private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) + { + if (EnvironmentConfiguration.DalamudForceMinHook) + useMinHook = true; + + address = HookManager.FollowJmp(address); + if (useMinHook) + this.compatHookImpl = new MinHookHook(address, detour, callingAssembly); + else + this.compatHookImpl = new ReloadedHook(address, detour, callingAssembly); + } + + /// + /// Gets a memory address of the target function. + /// + /// Hook is already disposed. + public IntPtr Address + { + get { - } - - /// - /// 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. - [Obsolete("Use Hook.FromAddress instead.")] - public Hook(IntPtr address, T detour, bool useMinHook) - : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A memory address to install a hook. - internal Hook(IntPtr address) - { - this.address = address; - } - - [Obsolete("Use Hook.FromAddress instead.")] - private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) - { - if (EnvironmentConfiguration.DalamudForceMinHook) - useMinHook = true; - - address = HookManager.FollowJmp(address); - if (useMinHook) - this.compatHookImpl = new MinHookHook(address, detour, callingAssembly); - else - this.compatHookImpl = new ReloadedHook(address, 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 virtual T Original => this.compatHookImpl != null ? this.compatHookImpl!.Original : throw new NotImplementedException(); - - /// - /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. - /// This can be called even after Dispose. - /// - public T OriginalDisposeSafe - { - get - { - if (this.compatHookImpl != null) - return this.compatHookImpl!.OriginalDisposeSafe; - if (this.IsDisposed) - return Marshal.GetDelegateForFunctionPointer(this.address); - return this.Original; - } - } - - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public virtual bool IsEnabled => this.compatHookImpl != null ? this.compatHookImpl!.IsEnabled : throw new NotImplementedException(); - - /// - /// Gets a value indicating whether or not the hook has been disposed. - /// - public bool IsDisposed { get; private set; } - - /// - public virtual string BackendName => this.compatHookImpl != null ? this.compatHookImpl!.BackendName : throw new NotImplementedException(); - - /// - /// Creates a hook by rewriting import table address. - /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - /// The hook with the supplied parameters. - public static unsafe Hook FromFunctionPointerVariable(IntPtr address, T detour) - { - return new FunctionPointerVariableHook(address, detour, Assembly.GetCallingAssembly()); - } - - /// - /// Creates a hook by rewriting import table address. - /// - /// Module to check for. Current process' main module if null. - /// Name of the DLL, including the extension. - /// Decorated name of the function. - /// Hint or ordinal. 0 to unspecify. - /// Callback function. Delegate must have a same original function prototype. - /// The hook with the supplied parameters. - public static unsafe Hook FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour) - { - module ??= Process.GetCurrentProcess().MainModule; - if (module == null) - throw new InvalidOperationException("Current module is null?"); - var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress; - var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4); - var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf(); - PeHeader.IMAGE_DATA_DIRECTORY* pDataDirectory; - if (isPe64) - { - var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf()); - pDataDirectory = &pOpt->ImportTable; - } - else - { - var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf()); - pDataDirectory = &pOpt->ImportTable; - } - - var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant(); - foreach (ref var importDescriptor in new Span( - (PeHeader.IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress), - (int)(pDataDirectory->Size / Marshal.SizeOf()))) - { - // Having all zero values signals the end of the table. We didn't find anything. - if (importDescriptor.Characteristics == 0) - throw new MissingMethodException("Specified dll not found"); - - // Skip invalid entries, just in case. - if (importDescriptor.Name == 0) - continue; - - // Name must be contained in this directory. - if (importDescriptor.Name < pDataDirectory->VirtualAddress) - continue; - var currentDllNameWithNullTerminator = Marshal.PtrToStringUTF8( - module.BaseAddress + (int)importDescriptor.Name, - (int)Math.Min(pDataDirectory->Size + pDataDirectory->VirtualAddress - importDescriptor.Name, moduleNameLowerWithNullTerminator.Length)); - - // Is this entry about the DLL that we're looking for? (Case insensitive) - if (currentDllNameWithNullTerminator.ToLowerInvariant() != moduleNameLowerWithNullTerminator) - continue; - - if (isPe64) - { - return new FunctionPointerVariableHook(FromImportHelper64(module.BaseAddress, ref importDescriptor, ref *pDataDirectory, functionName, hintOrOrdinal), detour, Assembly.GetCallingAssembly()); - } - else - { - return new FunctionPointerVariableHook(FromImportHelper32(module.BaseAddress, ref importDescriptor, ref *pDataDirectory, functionName, hintOrOrdinal), detour, Assembly.GetCallingAssembly()); - } - } - - throw new MissingMethodException("Specified dll not found"); - } - - /// - /// 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) - { - if (EnvironmentConfiguration.DalamudForceMinHook) - useMinHook = true; - - 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}"); - - procAddress = HookManager.FollowJmp(procAddress); - if (useMinHook) - return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); - else - return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly()); - } - - /// - /// 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 memory address to install a hook. - /// 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 FromAddress(IntPtr procAddress, T detour, bool useMinHook = false) - { - if (EnvironmentConfiguration.DalamudForceMinHook) - useMinHook = true; - - procAddress = HookManager.FollowJmp(procAddress); - if (useMinHook) - return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); - else - return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly()); - } - - /// - /// Remove a hook from the current process. - /// - public virtual void Dispose() - { - if (this.IsDisposed) - return; - - this.compatHookImpl?.Dispose(); - - this.IsDisposed = true; - } - - /// - /// Starts intercepting a call to the function. - /// - public virtual void Enable() - { - if (this.compatHookImpl != null) - this.compatHookImpl.Enable(); - else - throw new NotImplementedException(); - } - - /// - /// Stops intercepting a call to the function. - /// - public virtual void Disable() - { - if (this.compatHookImpl != null) - this.compatHookImpl.Disable(); - else - throw new NotImplementedException(); - } - - /// - /// Check if this object has been disposed already. - /// - protected void CheckDisposed() - { - if (this.IsDisposed) - { - throw new ObjectDisposedException(message: "Hook is already disposed", null); - } - } - - private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal) - { - var importLookupsOversizedSpan = new Span((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf())); - var importAddressesOversizedSpan = new Span((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf())); - - var functionNameWithNullTerminator = functionName + "\0"; - for (int i = 0, i_ = Math.Min(importLookupsOversizedSpan.Length, importAddressesOversizedSpan.Length); i < i_ && importLookupsOversizedSpan[i] != 0 && importAddressesOversizedSpan[i] != 0; i++) - { - var importLookup = importLookupsOversizedSpan[i]; - - // Is this entry importing by ordinals? A lot of socket functions are the case. - if ((importLookup & IMAGE_ORDINAL_FLAG32) != 0) - { - var ordinal = importLookup & ~IMAGE_ORDINAL_FLAG32; - - // Is this the entry? - if (hintOrOrdinal == 0 || ordinal != hintOrOrdinal) - continue; - - // Is this entry not importing by ordinals, and are we using hint exclusively to find the entry? - } - else - { - var hint = Marshal.ReadInt16(baseAddress + (int)importLookup); - - if (functionName.Length > 0) - { - // Is this the entry? - if (hint != hintOrOrdinal) - continue; - } - else - { - // Name must be contained in this directory. - var currentFunctionNameWithNullTerminator = Marshal.PtrToStringUTF8( - baseAddress + (int)importLookup + 2, - (int)Math.Min(dir.VirtualAddress + dir.Size - (uint)baseAddress - importLookup - 2, (uint)functionNameWithNullTerminator.Length)); - - // Is this entry about the function that we're looking for? - if (currentFunctionNameWithNullTerminator != functionNameWithNullTerminator) - continue; - } - } - - return baseAddress + (int)desc.FirstThunk + (i * Marshal.SizeOf()); - } - - throw new MissingMethodException("Specified method not found"); - } - - private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal) - { - var importLookupsOversizedSpan = new Span((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf())); - var importAddressesOversizedSpan = new Span((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf())); - - var functionNameWithNullTerminator = functionName + "\0"; - for (int i = 0, i_ = Math.Min(importLookupsOversizedSpan.Length, importAddressesOversizedSpan.Length); i < i_ && importLookupsOversizedSpan[i] != 0 && importAddressesOversizedSpan[i] != 0; i++) - { - var importLookup = importLookupsOversizedSpan[i]; - - // Is this entry importing by ordinals? A lot of socket functions are the case. - if ((importLookup & IMAGE_ORDINAL_FLAG64) != 0) - { - var ordinal = importLookup & ~IMAGE_ORDINAL_FLAG64; - - // Is this the entry? - if (hintOrOrdinal == 0 || ordinal != hintOrOrdinal) - continue; - - // Is this entry not importing by ordinals, and are we using hint exclusively to find the entry? - } - else - { - var hint = Marshal.ReadInt16(baseAddress + (int)importLookup); - - if (functionName.Length == 0) - { - // Is this the entry? - if (hint != hintOrOrdinal) - continue; - } - else - { - // Name must be contained in this directory. - var currentFunctionNameWithNullTerminator = Marshal.PtrToStringUTF8( - baseAddress + (int)importLookup + 2, - (int)Math.Min((ulong)dir.VirtualAddress + dir.Size - (ulong)baseAddress - importLookup - 2, (ulong)functionNameWithNullTerminator.Length)); - - // Is this entry about the function that we're looking for? - if (currentFunctionNameWithNullTerminator != functionNameWithNullTerminator) - continue; - } - } - - return baseAddress + (int)desc.FirstThunk + (i * Marshal.SizeOf()); - } - - throw new MissingMethodException("Specified method not found"); + 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 virtual T Original => this.compatHookImpl != null ? this.compatHookImpl!.Original : throw new NotImplementedException(); + + /// + /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. + /// This can be called even after Dispose. + /// + public T OriginalDisposeSafe + { + get + { + if (this.compatHookImpl != null) + return this.compatHookImpl!.OriginalDisposeSafe; + if (this.IsDisposed) + return Marshal.GetDelegateForFunctionPointer(this.address); + return this.Original; + } + } + + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public virtual bool IsEnabled => this.compatHookImpl != null ? this.compatHookImpl!.IsEnabled : throw new NotImplementedException(); + + /// + /// Gets a value indicating whether or not the hook has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + public virtual string BackendName => this.compatHookImpl != null ? this.compatHookImpl!.BackendName : throw new NotImplementedException(); + + /// + /// Creates a hook by rewriting import table address. + /// + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// The hook with the supplied parameters. + public static unsafe Hook FromFunctionPointerVariable(IntPtr address, T detour) + { + return new FunctionPointerVariableHook(address, detour, Assembly.GetCallingAssembly()); + } + + /// + /// Creates a hook by rewriting import table address. + /// + /// Module to check for. Current process' main module if null. + /// Name of the DLL, including the extension. + /// Decorated name of the function. + /// Hint or ordinal. 0 to unspecify. + /// Callback function. Delegate must have a same original function prototype. + /// The hook with the supplied parameters. + public static unsafe Hook FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour) + { + module ??= Process.GetCurrentProcess().MainModule; + if (module == null) + throw new InvalidOperationException("Current module is null?"); + var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress; + var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4); + var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf(); + PeHeader.IMAGE_DATA_DIRECTORY* pDataDirectory; + if (isPe64) + { + var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf()); + pDataDirectory = &pOpt->ImportTable; + } + else + { + var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf()); + pDataDirectory = &pOpt->ImportTable; + } + + var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant(); + foreach (ref var importDescriptor in new Span( + (PeHeader.IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress), + (int)(pDataDirectory->Size / Marshal.SizeOf()))) + { + // Having all zero values signals the end of the table. We didn't find anything. + if (importDescriptor.Characteristics == 0) + throw new MissingMethodException("Specified dll not found"); + + // Skip invalid entries, just in case. + if (importDescriptor.Name == 0) + continue; + + // Name must be contained in this directory. + if (importDescriptor.Name < pDataDirectory->VirtualAddress) + continue; + var currentDllNameWithNullTerminator = Marshal.PtrToStringUTF8( + module.BaseAddress + (int)importDescriptor.Name, + (int)Math.Min(pDataDirectory->Size + pDataDirectory->VirtualAddress - importDescriptor.Name, moduleNameLowerWithNullTerminator.Length)); + + // Is this entry about the DLL that we're looking for? (Case insensitive) + if (currentDllNameWithNullTerminator.ToLowerInvariant() != moduleNameLowerWithNullTerminator) + continue; + + if (isPe64) + { + return new FunctionPointerVariableHook(FromImportHelper64(module.BaseAddress, ref importDescriptor, ref *pDataDirectory, functionName, hintOrOrdinal), detour, Assembly.GetCallingAssembly()); + } + else + { + return new FunctionPointerVariableHook(FromImportHelper32(module.BaseAddress, ref importDescriptor, ref *pDataDirectory, functionName, hintOrOrdinal), detour, Assembly.GetCallingAssembly()); + } + } + + throw new MissingMethodException("Specified dll not found"); + } + + /// + /// 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) + { + if (EnvironmentConfiguration.DalamudForceMinHook) + useMinHook = true; + + 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}"); + + procAddress = HookManager.FollowJmp(procAddress); + if (useMinHook) + return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); + else + return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly()); + } + + /// + /// 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 memory address to install a hook. + /// 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 FromAddress(IntPtr procAddress, T detour, bool useMinHook = false) + { + if (EnvironmentConfiguration.DalamudForceMinHook) + useMinHook = true; + + procAddress = HookManager.FollowJmp(procAddress); + if (useMinHook) + return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); + else + return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly()); + } + + /// + /// Remove a hook from the current process. + /// + public virtual void Dispose() + { + if (this.IsDisposed) + return; + + this.compatHookImpl?.Dispose(); + + this.IsDisposed = true; + } + + /// + /// Starts intercepting a call to the function. + /// + public virtual void Enable() + { + if (this.compatHookImpl != null) + this.compatHookImpl.Enable(); + else + throw new NotImplementedException(); + } + + /// + /// Stops intercepting a call to the function. + /// + public virtual void Disable() + { + if (this.compatHookImpl != null) + this.compatHookImpl.Disable(); + else + throw new NotImplementedException(); + } + + /// + /// Check if this object has been disposed already. + /// + protected void CheckDisposed() + { + if (this.IsDisposed) + { + throw new ObjectDisposedException(message: "Hook is already disposed", null); + } + } + + private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal) + { + var importLookupsOversizedSpan = new Span((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf())); + var importAddressesOversizedSpan = new Span((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf())); + + var functionNameWithNullTerminator = functionName + "\0"; + for (int i = 0, i_ = Math.Min(importLookupsOversizedSpan.Length, importAddressesOversizedSpan.Length); i < i_ && importLookupsOversizedSpan[i] != 0 && importAddressesOversizedSpan[i] != 0; i++) + { + var importLookup = importLookupsOversizedSpan[i]; + + // Is this entry importing by ordinals? A lot of socket functions are the case. + if ((importLookup & IMAGE_ORDINAL_FLAG32) != 0) + { + var ordinal = importLookup & ~IMAGE_ORDINAL_FLAG32; + + // Is this the entry? + if (hintOrOrdinal == 0 || ordinal != hintOrOrdinal) + continue; + + // Is this entry not importing by ordinals, and are we using hint exclusively to find the entry? + } + else + { + var hint = Marshal.ReadInt16(baseAddress + (int)importLookup); + + if (functionName.Length > 0) + { + // Is this the entry? + if (hint != hintOrOrdinal) + continue; + } + else + { + // Name must be contained in this directory. + var currentFunctionNameWithNullTerminator = Marshal.PtrToStringUTF8( + baseAddress + (int)importLookup + 2, + (int)Math.Min(dir.VirtualAddress + dir.Size - (uint)baseAddress - importLookup - 2, (uint)functionNameWithNullTerminator.Length)); + + // Is this entry about the function that we're looking for? + if (currentFunctionNameWithNullTerminator != functionNameWithNullTerminator) + continue; + } + } + + return baseAddress + (int)desc.FirstThunk + (i * Marshal.SizeOf()); + } + + throw new MissingMethodException("Specified method not found"); + } + + private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal) + { + var importLookupsOversizedSpan = new Span((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf())); + var importAddressesOversizedSpan = new Span((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf())); + + var functionNameWithNullTerminator = functionName + "\0"; + for (int i = 0, i_ = Math.Min(importLookupsOversizedSpan.Length, importAddressesOversizedSpan.Length); i < i_ && importLookupsOversizedSpan[i] != 0 && importAddressesOversizedSpan[i] != 0; i++) + { + var importLookup = importLookupsOversizedSpan[i]; + + // Is this entry importing by ordinals? A lot of socket functions are the case. + if ((importLookup & IMAGE_ORDINAL_FLAG64) != 0) + { + var ordinal = importLookup & ~IMAGE_ORDINAL_FLAG64; + + // Is this the entry? + if (hintOrOrdinal == 0 || ordinal != hintOrOrdinal) + continue; + + // Is this entry not importing by ordinals, and are we using hint exclusively to find the entry? + } + else + { + var hint = Marshal.ReadInt16(baseAddress + (int)importLookup); + + if (functionName.Length == 0) + { + // Is this the entry? + if (hint != hintOrOrdinal) + continue; + } + else + { + // Name must be contained in this directory. + var currentFunctionNameWithNullTerminator = Marshal.PtrToStringUTF8( + baseAddress + (int)importLookup + 2, + (int)Math.Min((ulong)dir.VirtualAddress + dir.Size - (ulong)baseAddress - importLookup - 2, (ulong)functionNameWithNullTerminator.Length)); + + // Is this entry about the function that we're looking for? + if (currentFunctionNameWithNullTerminator != functionNameWithNullTerminator) + continue; + } + } + + return baseAddress + (int)desc.FirstThunk + (i * Marshal.SizeOf()); + } + + throw new MissingMethodException("Specified method not found"); + } } 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/FunctionPointerVariableHook.cs b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs index fcdba357a..3272f50b3 100644 --- a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs +++ b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs @@ -5,122 +5,121 @@ using System.Runtime.InteropServices; using Dalamud.Memory; -namespace Dalamud.Hooking.Internal +namespace Dalamud.Hooking.Internal; + +/// +/// Manages a hook with MinHook. +/// +/// Delegate type to represents a function prototype. This must be the same prototype as original function do. +internal class FunctionPointerVariableHook : Hook where T : Delegate { + private readonly IntPtr pfnOriginal; + private readonly T originalDelegate; + private readonly T detourDelegate; + + private bool enabled = false; + /// - /// Manages a hook with MinHook. + /// Initializes a new instance of the class. /// - /// Delegate type to represents a function prototype. This must be the same prototype as original function do. - internal class FunctionPointerVariableHook : Hook where T : Delegate + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// Calling assembly. + internal FunctionPointerVariableHook(IntPtr address, T detour, Assembly callingAssembly) + : base(address) { - private readonly IntPtr pfnOriginal; - private readonly T originalDelegate; - private readonly T detourDelegate; + lock (HookManager.HookEnableSyncRoot) + { + var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address); + if (!hasOtherHooks) + { + MemoryHelper.ReadRaw(this.Address, 0x32, out var original); + HookManager.Originals[this.Address] = original; + } - private bool enabled = false; + if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList)) + indexList = HookManager.MultiHookTracker[this.Address] = new(); - /// - /// Initializes a new instance of the class. - /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - /// Calling assembly. - internal FunctionPointerVariableHook(IntPtr address, T detour, Assembly callingAssembly) - : base(address) + this.pfnOriginal = Marshal.ReadIntPtr(this.Address); + this.originalDelegate = Marshal.GetDelegateForFunctionPointer(this.pfnOriginal); + this.detourDelegate = detour; + + // Add afterwards, so the hookIdent starts at 0. + indexList.Add(this); + + HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + } + } + + /// + public override T Original + { + get + { + this.CheckDisposed(); + return this.originalDelegate; + } + } + + /// + public override bool IsEnabled + { + get + { + this.CheckDisposed(); + return this.enabled; + } + } + + /// + public override string BackendName => "MinHook"; + + /// + public override void Dispose() + { + if (this.IsDisposed) + return; + + this.Disable(); + + var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); + HookManager.MultiHookTracker[this.Address][index] = null; + + base.Dispose(); + } + + /// + public override void Enable() + { + this.CheckDisposed(); + + if (!this.enabled) { lock (HookManager.HookEnableSyncRoot) { - var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address); - if (!hasOtherHooks) - { - MemoryHelper.ReadRaw(this.Address, 0x32, out var original); - HookManager.Originals[this.Address] = original; - } + if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), MemoryProtection.ExecuteReadWrite, out var oldProtect)) + throw new Win32Exception(Marshal.GetLastWin32Error()); - if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList)) - indexList = HookManager.MultiHookTracker[this.Address] = new(); - - this.pfnOriginal = Marshal.ReadIntPtr(this.Address); - this.originalDelegate = Marshal.GetDelegateForFunctionPointer(this.pfnOriginal); - this.detourDelegate = detour; - - // Add afterwards, so the hookIdent starts at 0. - indexList.Add(this); - - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + Marshal.WriteIntPtr(this.Address, Marshal.GetFunctionPointerForDelegate(this.detourDelegate)); + NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), oldProtect, out _); } } + } - /// - public override T Original + /// + public override void Disable() + { + this.CheckDisposed(); + + if (this.enabled) { - get + lock (HookManager.HookEnableSyncRoot) { - this.CheckDisposed(); - return this.originalDelegate; - } - } + if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), MemoryProtection.ExecuteReadWrite, out var oldProtect)) + throw new Win32Exception(Marshal.GetLastWin32Error()); - /// - public override bool IsEnabled - { - get - { - this.CheckDisposed(); - return this.enabled; - } - } - - /// - public override string BackendName => "MinHook"; - - /// - public override void Dispose() - { - if (this.IsDisposed) - return; - - this.Disable(); - - var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); - HookManager.MultiHookTracker[this.Address][index] = null; - - base.Dispose(); - } - - /// - public override void Enable() - { - this.CheckDisposed(); - - if (!this.enabled) - { - lock (HookManager.HookEnableSyncRoot) - { - if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), MemoryProtection.ExecuteReadWrite, out var oldProtect)) - throw new Win32Exception(Marshal.GetLastWin32Error()); - - Marshal.WriteIntPtr(this.Address, Marshal.GetFunctionPointerForDelegate(this.detourDelegate)); - NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), oldProtect, out _); - } - } - } - - /// - public override void Disable() - { - this.CheckDisposed(); - - if (this.enabled) - { - lock (HookManager.HookEnableSyncRoot) - { - if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), MemoryProtection.ExecuteReadWrite, out var oldProtect)) - throw new Win32Exception(Marshal.GetLastWin32Error()); - - Marshal.WriteIntPtr(this.Address, this.pfnOriginal); - NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), oldProtect, out _); - } + Marshal.WriteIntPtr(this.Address, this.pfnOriginal); + NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), oldProtect, out _); } } } 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 51047b97e..303802ff6 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -8,144 +8,143 @@ using Dalamud.Logging.Internal; using Dalamud.Memory; using Iced.Intel; -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. +/// +[ServiceManager.EarlyLoadedService] +internal class HookManager : IDisposable, IServiceType { - /// - /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. - /// - [ServiceManager.EarlyLoadedService] - internal class HookManager : IDisposable, IServiceType + private static readonly ModuleLog Log = new("HM"); + + [ServiceManager.ServiceConstructor] + private HookManager() { - private static readonly ModuleLog Log = new("HM"); + } - [ServiceManager.ServiceConstructor] - private HookManager() + /// + /// Gets sync root object for hook enabling/disabling. + /// + internal static object HookEnableSyncRoot { get; } = new(); + + /// + /// Gets a static list of tracked and registered hooks. + /// + internal static ConcurrentDictionary TrackedHooks { get; } = new(); + + /// + /// Gets a static dictionary of original code for a hooked address. + /// + internal static ConcurrentDictionary Originals { get; } = new(); + + /// + /// Gets a static dictionary of the number of hooks on a given address. + /// + internal static ConcurrentDictionary> 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 sync root object for hook enabling/disabling. - /// - internal static object HookEnableSyncRoot { get; } = new(); - - /// - /// Gets a static list of tracked and registered hooks. - /// - internal static ConcurrentDictionary TrackedHooks { get; } = new(); - - /// - /// Gets a static dictionary of original code for a hooked address. - /// - internal static ConcurrentDictionary Originals { get; } = new(); - - /// - /// Gets a static dictionary of the number of hooks on a given address. - /// - internal static ConcurrentDictionary> 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; - } - - if (address.ToInt64() <= 0) - throw new InvalidOperationException($"Address was <= 0, this can't be happening?! ({address:X})"); - - 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; + if (address.ToInt64() <= 0) + throw new InvalidOperationException($"Address was <= 0, this can't be happening?! ({address:X})"); + + 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/Hooking/Internal/MinHookHook.cs b/Dalamud/Hooking/Internal/MinHookHook.cs index ce4478ba8..0da289371 100644 --- a/Dalamud/Hooking/Internal/MinHookHook.cs +++ b/Dalamud/Hooking/Internal/MinHookHook.cs @@ -3,113 +3,112 @@ using System.Reflection; using Dalamud.Memory; -namespace Dalamud.Hooking.Internal +namespace Dalamud.Hooking.Internal; + +/// +/// Manages a hook with MinHook. +/// +/// Delegate type to represents a function prototype. This must be the same prototype as original function do. +internal class MinHookHook : Hook where T : Delegate { + private readonly MinSharp.Hook minHookImpl; + /// - /// Manages a hook with MinHook. + /// Initializes a new instance of the class. /// - /// Delegate type to represents a function prototype. This must be the same prototype as original function do. - internal class MinHookHook : Hook where T : Delegate + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// Calling assembly. + internal MinHookHook(IntPtr address, T detour, Assembly callingAssembly) + : base(address) { - private readonly MinSharp.Hook minHookImpl; + lock (HookManager.HookEnableSyncRoot) + { + var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address); + if (!hasOtherHooks) + { + MemoryHelper.ReadRaw(this.Address, 0x32, out var original); + HookManager.Originals[this.Address] = original; + } - /// - /// Initializes a new instance of the class. - /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - /// Calling assembly. - internal MinHookHook(IntPtr address, T detour, Assembly callingAssembly) - : base(address) + if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList)) + indexList = HookManager.MultiHookTracker[this.Address] = new(); + + var index = (ulong)indexList.Count; + + this.minHookImpl = new MinSharp.Hook(this.Address, detour, index); + + // Add afterwards, so the hookIdent starts at 0. + indexList.Add(this); + + HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + } + } + + /// + public override T Original + { + get + { + this.CheckDisposed(); + return this.minHookImpl.Original; + } + } + + /// + public override bool IsEnabled + { + get + { + this.CheckDisposed(); + return this.minHookImpl.Enabled; + } + } + + /// + public override string BackendName => "MinHook"; + + /// + public override void Dispose() + { + if (this.IsDisposed) + return; + + lock (HookManager.HookEnableSyncRoot) + { + this.minHookImpl.Dispose(); + + var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); + HookManager.MultiHookTracker[this.Address][index] = null; + } + + base.Dispose(); + } + + /// + public override void Enable() + { + this.CheckDisposed(); + + if (!this.minHookImpl.Enabled) { lock (HookManager.HookEnableSyncRoot) { - var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address); - if (!hasOtherHooks) - { - MemoryHelper.ReadRaw(this.Address, 0x32, out var original); - HookManager.Originals[this.Address] = original; - } - - if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList)) - indexList = HookManager.MultiHookTracker[this.Address] = new(); - - var index = (ulong)indexList.Count; - - this.minHookImpl = new MinSharp.Hook(this.Address, detour, index); - - // Add afterwards, so the hookIdent starts at 0. - indexList.Add(this); - - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + this.minHookImpl.Enable(); } } + } - /// - public override T Original + /// + public override void Disable() + { + this.CheckDisposed(); + + if (this.minHookImpl.Enabled) { - get - { - this.CheckDisposed(); - return this.minHookImpl.Original; - } - } - - /// - public override bool IsEnabled - { - get - { - this.CheckDisposed(); - return this.minHookImpl.Enabled; - } - } - - /// - public override string BackendName => "MinHook"; - - /// - public override void Dispose() - { - if (this.IsDisposed) - return; - lock (HookManager.HookEnableSyncRoot) { - this.minHookImpl.Dispose(); - - var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); - HookManager.MultiHookTracker[this.Address][index] = null; - } - - base.Dispose(); - } - - /// - public override void Enable() - { - this.CheckDisposed(); - - if (!this.minHookImpl.Enabled) - { - lock (HookManager.HookEnableSyncRoot) - { - this.minHookImpl.Enable(); - } - } - } - - /// - public override void Disable() - { - this.CheckDisposed(); - - if (this.minHookImpl.Enabled) - { - lock (HookManager.HookEnableSyncRoot) - { - this.minHookImpl.Disable(); - } + this.minHookImpl.Disable(); } } } diff --git a/Dalamud/Hooking/Internal/PeHeader.cs b/Dalamud/Hooking/Internal/PeHeader.cs index 89898ccb9..b0e1770f5 100644 --- a/Dalamud/Hooking/Internal/PeHeader.cs +++ b/Dalamud/Hooking/Internal/PeHeader.cs @@ -2,391 +2,390 @@ using System; using System.Runtime.InteropServices; #pragma warning disable -namespace Dalamud.Hooking.Internal +namespace Dalamud.Hooking.Internal; + +internal class PeHeader { - internal class PeHeader + public struct IMAGE_DOS_HEADER { - public struct IMAGE_DOS_HEADER + public UInt16 e_magic; + public UInt16 e_cblp; + public UInt16 e_cp; + public UInt16 e_crlc; + public UInt16 e_cparhdr; + public UInt16 e_minalloc; + public UInt16 e_maxalloc; + public UInt16 e_ss; + public UInt16 e_sp; + public UInt16 e_csum; + public UInt16 e_ip; + public UInt16 e_cs; + public UInt16 e_lfarlc; + public UInt16 e_ovno; + public UInt16 e_res_0; + public UInt16 e_res_1; + public UInt16 e_res_2; + public UInt16 e_res_3; + public UInt16 e_oemid; + public UInt16 e_oeminfo; + public UInt16 e_res2_0; + public UInt16 e_res2_1; + public UInt16 e_res2_2; + public UInt16 e_res2_3; + public UInt16 e_res2_4; + public UInt16 e_res2_5; + public UInt16 e_res2_6; + public UInt16 e_res2_7; + public UInt16 e_res2_8; + public UInt16 e_res2_9; + public UInt32 e_lfanew; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY + { + public UInt32 VirtualAddress; + public UInt32 Size; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER32 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt32 BaseOfData; + public UInt32 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt32 SizeOfStackReserve; + public UInt32 SizeOfStackCommit; + public UInt32 SizeOfHeapReserve; + public UInt32 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER64 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt64 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt64 SizeOfStackReserve; + public UInt64 SizeOfStackCommit; + public UInt64 SizeOfHeapReserve; + public UInt64 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_FILE_HEADER + { + public UInt16 Machine; + public UInt16 NumberOfSections; + public UInt32 TimeDateStamp; + public UInt32 PointerToSymbolTable; + public UInt32 NumberOfSymbols; + public UInt16 SizeOfOptionalHeader; + public UInt16 Characteristics; + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_SECTION_HEADER + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] Name; + [FieldOffset(8)] + public UInt32 VirtualSize; + [FieldOffset(12)] + public UInt32 VirtualAddress; + [FieldOffset(16)] + public UInt32 SizeOfRawData; + [FieldOffset(20)] + public UInt32 PointerToRawData; + [FieldOffset(24)] + public UInt32 PointerToRelocations; + [FieldOffset(28)] + public UInt32 PointerToLinenumbers; + [FieldOffset(32)] + public UInt16 NumberOfRelocations; + [FieldOffset(34)] + public UInt16 NumberOfLinenumbers; + [FieldOffset(36)] + public DataSectionFlags Characteristics; + + public string Section { - public UInt16 e_magic; - public UInt16 e_cblp; - public UInt16 e_cp; - public UInt16 e_crlc; - public UInt16 e_cparhdr; - public UInt16 e_minalloc; - public UInt16 e_maxalloc; - public UInt16 e_ss; - public UInt16 e_sp; - public UInt16 e_csum; - public UInt16 e_ip; - public UInt16 e_cs; - public UInt16 e_lfarlc; - public UInt16 e_ovno; - public UInt16 e_res_0; - public UInt16 e_res_1; - public UInt16 e_res_2; - public UInt16 e_res_3; - public UInt16 e_oemid; - public UInt16 e_oeminfo; - public UInt16 e_res2_0; - public UInt16 e_res2_1; - public UInt16 e_res2_2; - public UInt16 e_res2_3; - public UInt16 e_res2_4; - public UInt16 e_res2_5; - public UInt16 e_res2_6; - public UInt16 e_res2_7; - public UInt16 e_res2_8; - public UInt16 e_res2_9; - public UInt32 e_lfanew; - } - - [StructLayout(LayoutKind.Sequential)] - public struct IMAGE_DATA_DIRECTORY - { - public UInt32 VirtualAddress; - public UInt32 Size; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_OPTIONAL_HEADER32 - { - public UInt16 Magic; - public Byte MajorLinkerVersion; - public Byte MinorLinkerVersion; - public UInt32 SizeOfCode; - public UInt32 SizeOfInitializedData; - public UInt32 SizeOfUninitializedData; - public UInt32 AddressOfEntryPoint; - public UInt32 BaseOfCode; - public UInt32 BaseOfData; - public UInt32 ImageBase; - public UInt32 SectionAlignment; - public UInt32 FileAlignment; - public UInt16 MajorOperatingSystemVersion; - public UInt16 MinorOperatingSystemVersion; - public UInt16 MajorImageVersion; - public UInt16 MinorImageVersion; - public UInt16 MajorSubsystemVersion; - public UInt16 MinorSubsystemVersion; - public UInt32 Win32VersionValue; - public UInt32 SizeOfImage; - public UInt32 SizeOfHeaders; - public UInt32 CheckSum; - public UInt16 Subsystem; - public UInt16 DllCharacteristics; - public UInt32 SizeOfStackReserve; - public UInt32 SizeOfStackCommit; - public UInt32 SizeOfHeapReserve; - public UInt32 SizeOfHeapCommit; - public UInt32 LoaderFlags; - public UInt32 NumberOfRvaAndSizes; - - public IMAGE_DATA_DIRECTORY ExportTable; - public IMAGE_DATA_DIRECTORY ImportTable; - public IMAGE_DATA_DIRECTORY ResourceTable; - public IMAGE_DATA_DIRECTORY ExceptionTable; - public IMAGE_DATA_DIRECTORY CertificateTable; - public IMAGE_DATA_DIRECTORY BaseRelocationTable; - public IMAGE_DATA_DIRECTORY Debug; - public IMAGE_DATA_DIRECTORY Architecture; - public IMAGE_DATA_DIRECTORY GlobalPtr; - public IMAGE_DATA_DIRECTORY TLSTable; - public IMAGE_DATA_DIRECTORY LoadConfigTable; - public IMAGE_DATA_DIRECTORY BoundImport; - public IMAGE_DATA_DIRECTORY IAT; - public IMAGE_DATA_DIRECTORY DelayImportDescriptor; - public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; - public IMAGE_DATA_DIRECTORY Reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_OPTIONAL_HEADER64 - { - public UInt16 Magic; - public Byte MajorLinkerVersion; - public Byte MinorLinkerVersion; - public UInt32 SizeOfCode; - public UInt32 SizeOfInitializedData; - public UInt32 SizeOfUninitializedData; - public UInt32 AddressOfEntryPoint; - public UInt32 BaseOfCode; - public UInt64 ImageBase; - public UInt32 SectionAlignment; - public UInt32 FileAlignment; - public UInt16 MajorOperatingSystemVersion; - public UInt16 MinorOperatingSystemVersion; - public UInt16 MajorImageVersion; - public UInt16 MinorImageVersion; - public UInt16 MajorSubsystemVersion; - public UInt16 MinorSubsystemVersion; - public UInt32 Win32VersionValue; - public UInt32 SizeOfImage; - public UInt32 SizeOfHeaders; - public UInt32 CheckSum; - public UInt16 Subsystem; - public UInt16 DllCharacteristics; - public UInt64 SizeOfStackReserve; - public UInt64 SizeOfStackCommit; - public UInt64 SizeOfHeapReserve; - public UInt64 SizeOfHeapCommit; - public UInt32 LoaderFlags; - public UInt32 NumberOfRvaAndSizes; - - public IMAGE_DATA_DIRECTORY ExportTable; - public IMAGE_DATA_DIRECTORY ImportTable; - public IMAGE_DATA_DIRECTORY ResourceTable; - public IMAGE_DATA_DIRECTORY ExceptionTable; - public IMAGE_DATA_DIRECTORY CertificateTable; - public IMAGE_DATA_DIRECTORY BaseRelocationTable; - public IMAGE_DATA_DIRECTORY Debug; - public IMAGE_DATA_DIRECTORY Architecture; - public IMAGE_DATA_DIRECTORY GlobalPtr; - public IMAGE_DATA_DIRECTORY TLSTable; - public IMAGE_DATA_DIRECTORY LoadConfigTable; - public IMAGE_DATA_DIRECTORY BoundImport; - public IMAGE_DATA_DIRECTORY IAT; - public IMAGE_DATA_DIRECTORY DelayImportDescriptor; - public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; - public IMAGE_DATA_DIRECTORY Reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_FILE_HEADER - { - public UInt16 Machine; - public UInt16 NumberOfSections; - public UInt32 TimeDateStamp; - public UInt32 PointerToSymbolTable; - public UInt32 NumberOfSymbols; - public UInt16 SizeOfOptionalHeader; - public UInt16 Characteristics; - } - - [StructLayout(LayoutKind.Explicit)] - public struct IMAGE_SECTION_HEADER - { - [FieldOffset(0)] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public char[] Name; - [FieldOffset(8)] - public UInt32 VirtualSize; - [FieldOffset(12)] - public UInt32 VirtualAddress; - [FieldOffset(16)] - public UInt32 SizeOfRawData; - [FieldOffset(20)] - public UInt32 PointerToRawData; - [FieldOffset(24)] - public UInt32 PointerToRelocations; - [FieldOffset(28)] - public UInt32 PointerToLinenumbers; - [FieldOffset(32)] - public UInt16 NumberOfRelocations; - [FieldOffset(34)] - public UInt16 NumberOfLinenumbers; - [FieldOffset(36)] - public DataSectionFlags Characteristics; - - public string Section - { - get { return new string(Name); } - } - } - - [Flags] - public enum DataSectionFlags : uint - { - /// - /// Reserved for future use. - /// - TypeReg = 0x00000000, - /// - /// Reserved for future use. - /// - TypeDsect = 0x00000001, - /// - /// Reserved for future use. - /// - TypeNoLoad = 0x00000002, - /// - /// Reserved for future use. - /// - TypeGroup = 0x00000004, - /// - /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. - /// - TypeNoPadded = 0x00000008, - /// - /// Reserved for future use. - /// - TypeCopy = 0x00000010, - /// - /// The section contains executable code. - /// - ContentCode = 0x00000020, - /// - /// The section contains initialized data. - /// - ContentInitializedData = 0x00000040, - /// - /// The section contains uninitialized data. - /// - ContentUninitializedData = 0x00000080, - /// - /// Reserved for future use. - /// - LinkOther = 0x00000100, - /// - /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. - /// - LinkInfo = 0x00000200, - /// - /// Reserved for future use. - /// - TypeOver = 0x00000400, - /// - /// The section will not become part of the image. This is valid only for object files. - /// - LinkRemove = 0x00000800, - /// - /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. - /// - LinkComDat = 0x00001000, - /// - /// Reset speculative exceptions handling bits in the TLB entries for this section. - /// - NoDeferSpecExceptions = 0x00004000, - /// - /// The section contains data referenced through the global pointer (GP). - /// - RelativeGP = 0x00008000, - /// - /// Reserved for future use. - /// - MemPurgeable = 0x00020000, - /// - /// Reserved for future use. - /// - Memory16Bit = 0x00020000, - /// - /// Reserved for future use. - /// - MemoryLocked = 0x00040000, - /// - /// Reserved for future use. - /// - MemoryPreload = 0x00080000, - /// - /// Align data on a 1-byte boundary. Valid only for object files. - /// - Align1Bytes = 0x00100000, - /// - /// Align data on a 2-byte boundary. Valid only for object files. - /// - Align2Bytes = 0x00200000, - /// - /// Align data on a 4-byte boundary. Valid only for object files. - /// - Align4Bytes = 0x00300000, - /// - /// Align data on an 8-byte boundary. Valid only for object files. - /// - Align8Bytes = 0x00400000, - /// - /// Align data on a 16-byte boundary. Valid only for object files. - /// - Align16Bytes = 0x00500000, - /// - /// Align data on a 32-byte boundary. Valid only for object files. - /// - Align32Bytes = 0x00600000, - /// - /// Align data on a 64-byte boundary. Valid only for object files. - /// - Align64Bytes = 0x00700000, - /// - /// Align data on a 128-byte boundary. Valid only for object files. - /// - Align128Bytes = 0x00800000, - /// - /// Align data on a 256-byte boundary. Valid only for object files. - /// - Align256Bytes = 0x00900000, - /// - /// Align data on a 512-byte boundary. Valid only for object files. - /// - Align512Bytes = 0x00A00000, - /// - /// Align data on a 1024-byte boundary. Valid only for object files. - /// - Align1024Bytes = 0x00B00000, - /// - /// Align data on a 2048-byte boundary. Valid only for object files. - /// - Align2048Bytes = 0x00C00000, - /// - /// Align data on a 4096-byte boundary. Valid only for object files. - /// - Align4096Bytes = 0x00D00000, - /// - /// Align data on an 8192-byte boundary. Valid only for object files. - /// - Align8192Bytes = 0x00E00000, - /// - /// The section contains extended relocations. - /// - LinkExtendedRelocationOverflow = 0x01000000, - /// - /// The section can be discarded as needed. - /// - MemoryDiscardable = 0x02000000, - /// - /// The section cannot be cached. - /// - MemoryNotCached = 0x04000000, - /// - /// The section is not pageable. - /// - MemoryNotPaged = 0x08000000, - /// - /// The section can be shared in memory. - /// - MemoryShared = 0x10000000, - /// - /// The section can be executed as code. - /// - MemoryExecute = 0x20000000, - /// - /// The section can be read. - /// - MemoryRead = 0x40000000, - /// - /// The section can be written to. - /// - MemoryWrite = 0x80000000 - } - - [StructLayout(LayoutKind.Explicit)] - public struct IMAGE_IMPORT_DESCRIPTOR - { - [FieldOffset(0)] - public uint Characteristics; - - [FieldOffset(0)] - public uint OriginalFirstThunk; - - [FieldOffset(4)] - public uint TimeDateStamp; - - [FieldOffset(8)] - public uint ForwarderChain; - - [FieldOffset(12)] - public uint Name; - - [FieldOffset(16)] - public uint FirstThunk; + get { return new string(Name); } } } + + [Flags] + public enum DataSectionFlags : uint + { + /// + /// Reserved for future use. + /// + TypeReg = 0x00000000, + /// + /// Reserved for future use. + /// + TypeDsect = 0x00000001, + /// + /// Reserved for future use. + /// + TypeNoLoad = 0x00000002, + /// + /// Reserved for future use. + /// + TypeGroup = 0x00000004, + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPadded = 0x00000008, + /// + /// Reserved for future use. + /// + TypeCopy = 0x00000010, + /// + /// The section contains executable code. + /// + ContentCode = 0x00000020, + /// + /// The section contains initialized data. + /// + ContentInitializedData = 0x00000040, + /// + /// The section contains uninitialized data. + /// + ContentUninitializedData = 0x00000080, + /// + /// Reserved for future use. + /// + LinkOther = 0x00000100, + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LinkInfo = 0x00000200, + /// + /// Reserved for future use. + /// + TypeOver = 0x00000400, + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LinkRemove = 0x00000800, + /// + /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. + /// + LinkComDat = 0x00001000, + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NoDeferSpecExceptions = 0x00004000, + /// + /// The section contains data referenced through the global pointer (GP). + /// + RelativeGP = 0x00008000, + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + /// + /// Reserved for future use. + /// + Memory16Bit = 0x00020000, + /// + /// Reserved for future use. + /// + MemoryLocked = 0x00040000, + /// + /// Reserved for future use. + /// + MemoryPreload = 0x00080000, + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + /// + /// Align data on an 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, + /// + /// Align data on a 2048-byte boundary. Valid only for object files. + /// + Align2048Bytes = 0x00C00000, + /// + /// Align data on a 4096-byte boundary. Valid only for object files. + /// + Align4096Bytes = 0x00D00000, + /// + /// Align data on an 8192-byte boundary. Valid only for object files. + /// + Align8192Bytes = 0x00E00000, + /// + /// The section contains extended relocations. + /// + LinkExtendedRelocationOverflow = 0x01000000, + /// + /// The section can be discarded as needed. + /// + MemoryDiscardable = 0x02000000, + /// + /// The section cannot be cached. + /// + MemoryNotCached = 0x04000000, + /// + /// The section is not pageable. + /// + MemoryNotPaged = 0x08000000, + /// + /// The section can be shared in memory. + /// + MemoryShared = 0x10000000, + /// + /// The section can be executed as code. + /// + MemoryExecute = 0x20000000, + /// + /// The section can be read. + /// + MemoryRead = 0x40000000, + /// + /// The section can be written to. + /// + MemoryWrite = 0x80000000 + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_IMPORT_DESCRIPTOR + { + [FieldOffset(0)] + public uint Characteristics; + + [FieldOffset(0)] + public uint OriginalFirstThunk; + + [FieldOffset(4)] + public uint TimeDateStamp; + + [FieldOffset(8)] + public uint ForwarderChain; + + [FieldOffset(12)] + public uint Name; + + [FieldOffset(16)] + public uint FirstThunk; + } } diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs index 68c23e14f..77c0c9c19 100644 --- a/Dalamud/Hooking/Internal/ReloadedHook.cs +++ b/Dalamud/Hooking/Internal/ReloadedHook.cs @@ -4,101 +4,100 @@ using System.Reflection; using Dalamud.Memory; using Reloaded.Hooks; -namespace Dalamud.Hooking.Internal +namespace Dalamud.Hooking.Internal; + +/// +/// Class facilitating hooks via reloaded. +/// +/// Delegate of the hook. +internal class ReloadedHook : Hook where T : Delegate { + private readonly Reloaded.Hooks.Definitions.IHook hookImpl; + /// - /// Class facilitating hooks via reloaded. + /// Initializes a new instance of the class. /// - /// Delegate of the hook. - internal class ReloadedHook : Hook where T : Delegate + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// Calling assembly. + internal ReloadedHook(IntPtr address, T detour, Assembly callingAssembly) + : base(address) { - private readonly Reloaded.Hooks.Definitions.IHook hookImpl; - - /// - /// Initializes a new instance of the class. - /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - /// Calling assembly. - internal ReloadedHook(IntPtr address, T detour, Assembly callingAssembly) - : base(address) + lock (HookManager.HookEnableSyncRoot) { - lock (HookManager.HookEnableSyncRoot) + var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address); + if (!hasOtherHooks) { - var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address); - if (!hasOtherHooks) - { - MemoryHelper.ReadRaw(this.Address, 0x32, out var original); - HookManager.Originals[this.Address] = original; - } - - this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); - this.hookImpl.Activate(); - this.hookImpl.Disable(); - - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + MemoryHelper.ReadRaw(this.Address, 0x32, out var original); + HookManager.Originals[this.Address] = original; } + + this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); + this.hookImpl.Activate(); + this.hookImpl.Disable(); + + HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); } + } - /// - public override T Original + /// + public override T Original + { + get { - get - { - this.CheckDisposed(); - return this.hookImpl.OriginalFunction; - } + this.CheckDisposed(); + return this.hookImpl.OriginalFunction; } + } - /// - public override bool IsEnabled + /// + public override bool IsEnabled + { + get { - get - { - this.CheckDisposed(); - return this.hookImpl.IsHookEnabled; - } + this.CheckDisposed(); + return this.hookImpl.IsHookEnabled; } + } - /// - public override string BackendName => "Reloaded"; + /// + public override string BackendName => "Reloaded"; - /// - public override void Dispose() + /// + public override void Dispose() + { + if (this.IsDisposed) + return; + + this.Disable(); + + base.Dispose(); + } + + /// + public override void Enable() + { + this.CheckDisposed(); + + lock (HookManager.HookEnableSyncRoot) { - if (this.IsDisposed) + if (!this.hookImpl.IsHookEnabled) + this.hookImpl.Enable(); + } + } + + /// + public override void Disable() + { + this.CheckDisposed(); + + lock (HookManager.HookEnableSyncRoot) + { + if (!this.hookImpl.IsHookActivated) return; - this.Disable(); - - base.Dispose(); - } - - /// - public override void Enable() - { - this.CheckDisposed(); - - lock (HookManager.HookEnableSyncRoot) - { - if (!this.hookImpl.IsHookEnabled) - this.hookImpl.Enable(); - } - } - - /// - public override void Disable() - { - this.CheckDisposed(); - - lock (HookManager.HookEnableSyncRoot) - { - if (!this.hookImpl.IsHookActivated) - return; - - if (this.hookImpl.IsHookEnabled) - this.hookImpl.Disable(); - } + if (this.hookImpl.IsHookEnabled) + this.hookImpl.Disable(); } } } diff --git a/Dalamud/Interface/Animation/AnimUtil.cs b/Dalamud/Interface/Animation/AnimUtil.cs index cff36b21c..35a797df8 100644 --- a/Dalamud/Interface/Animation/AnimUtil.cs +++ b/Dalamud/Interface/Animation/AnimUtil.cs @@ -1,36 +1,35 @@ 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..2ac040143 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -2,116 +2,115 @@ 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..ebfc1341c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -1,27 +1,26 @@ 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..d49715893 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -1,27 +1,26 @@ 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..30e21cca8 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -1,33 +1,32 @@ 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..fa90c9fd3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -1,29 +1,28 @@ 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..bc55bff70 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -1,27 +1,26 @@ 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..b0c76b725 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -1,35 +1,34 @@ 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..a4d427710 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -1,27 +1,26 @@ 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..a3700d6c5 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -1,27 +1,26 @@ 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..20122efc6 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -1,27 +1,26 @@ 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..553996303 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -1,27 +1,26 @@ 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..356beb13d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -1,27 +1,26 @@ 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..9fbe48b26 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -1,27 +1,26 @@ 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..d73fca30e 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -1,33 +1,32 @@ 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..f83a4eb47 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -1,27 +1,26 @@ 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..1576f697b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -1,27 +1,26 @@ 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 e1a3356fa..93a41e877 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; internal set; } = new(1f, 1f, .4f, 1f); + /// + /// Gets yellow used in dalamud. + /// + public static Vector4 DalamudYellow { get; internal set; } = new(1f, 1f, .4f, 1f); - /// - /// Gets violet used in dalamud. - /// - public static Vector4 DalamudViolet { get; internal set; } = new(0.770f, 0.700f, 0.965f, 1.000f); + /// + /// Gets violet used in dalamud. + /// + public static Vector4 DalamudViolet { get; internal set; } = 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; internal set; } = new(0.4f, 0.4f, 0.4f, 1f); + /// + /// Gets parsed grey. + /// + public static Vector4 ParsedGrey { get; internal set; } = new(0.4f, 0.4f, 0.4f, 1f); - /// - /// Gets parsed green. - /// - public static Vector4 ParsedGreen { get; internal set; } = new(0.117f, 1f, 0f, 1f); + /// + /// Gets parsed green. + /// + public static Vector4 ParsedGreen { get; internal set; } = new(0.117f, 1f, 0f, 1f); - /// - /// Gets parsed blue. - /// - public static Vector4 ParsedBlue { get; internal set; } = new(0f, 0.439f, 1f, 1f); + /// + /// Gets parsed blue. + /// + public static Vector4 ParsedBlue { get; internal set; } = new(0f, 0.439f, 1f, 1f); - /// - /// Gets parsed purple. - /// - public static Vector4 ParsedPurple { get; internal set; } = new(0.639f, 0.207f, 0.933f, 1f); + /// + /// Gets parsed purple. + /// + public static Vector4 ParsedPurple { get; internal set; } = new(0.639f, 0.207f, 0.933f, 1f); - /// - /// Gets parsed orange. - /// - public static Vector4 ParsedOrange { get; internal set; } = new(1f, 0.501f, 0f, 1f); + /// + /// Gets parsed orange. + /// + public static Vector4 ParsedOrange { get; internal set; } = new(1f, 0.501f, 0f, 1f); - /// - /// Gets parsed pink. - /// - public static Vector4 ParsedPink { get; internal set; } = new(0.886f, 0.407f, 0.658f, 1f); + /// + /// Gets parsed pink. + /// + public static Vector4 ParsedPink { get; internal set; } = new(0.886f, 0.407f, 0.658f, 1f); - /// - /// Gets parsed gold. - /// - public static Vector4 ParsedGold { get; internal set; } = new(0.898f, 0.8f, 0.501f, 1f); - } + /// + /// Gets parsed gold. + /// + public static Vector4 ParsedGold { get; internal set; } = 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.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs index bdf14b430..64f3d01eb 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs @@ -2,70 +2,69 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Component for toggle buttons. +/// +public static partial class ImGuiComponents { /// - /// Component for toggle buttons. + /// Draw a toggle button. /// - public static partial class ImGuiComponents + /// The id of the button. + /// The state of the switch. + /// If the button has been interacted with this frame. + public static bool ToggleButton(string id, ref bool v) { - /// - /// Draw a toggle button. - /// - /// The id of the button. - /// The state of the switch. - /// If the button has been interacted with this frame. - public static bool ToggleButton(string id, ref bool v) + var colors = ImGui.GetStyle().Colors; + var p = ImGui.GetCursorScreenPos(); + var drawList = ImGui.GetWindowDrawList(); + + var height = ImGui.GetFrameHeight(); + var width = height * 1.55f; + var radius = height * 0.50f; + + // TODO: animate + + var changed = false; + ImGui.InvisibleButton(id, new Vector2(width, height)); + if (ImGui.IsItemClicked()) { - var colors = ImGui.GetStyle().Colors; - var p = ImGui.GetCursorScreenPos(); - var drawList = ImGui.GetWindowDrawList(); - - var height = ImGui.GetFrameHeight(); - var width = height * 1.55f; - var radius = height * 0.50f; - - // TODO: animate - - var changed = false; - ImGui.InvisibleButton(id, new Vector2(width, height)); - if (ImGui.IsItemClicked()) - { - v = !v; - changed = true; - } - - if (ImGui.IsItemHovered()) - drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); - else - drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); - drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); - - return changed; + v = !v; + changed = true; } - /// - /// Draw a disabled toggle button. - /// - /// The id of the button. - /// The state of the switch. - public static void DisabledToggleButton(string id, bool v) - { - var colors = ImGui.GetStyle().Colors; - var p = ImGui.GetCursorScreenPos(); - var drawList = ImGui.GetWindowDrawList(); + if (ImGui.IsItemHovered()) + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); + else + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); - var height = ImGui.GetFrameHeight(); - var width = height * 1.55f; - var radius = height * 0.50f; + return changed; + } - // TODO: animate - ImGui.InvisibleButton(id, new Vector2(width, height)); + /// + /// Draw a disabled toggle button. + /// + /// The id of the button. + /// The state of the switch. + public static void DisabledToggleButton(string id, bool v) + { + var colors = ImGui.GetStyle().Colors; + var p = ImGui.GetCursorScreenPos(); + var drawList = ImGui.GetWindowDrawList(); - var dimFactor = 0.5f; + var height = ImGui.GetFrameHeight(); + var width = height * 1.55f; + var radius = height * 0.50f; - drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); - drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); - } + // TODO: animate + ImGui.InvisibleButton(id, new Vector2(width, height)); + + var dimFactor = 0.5f; + + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); } } 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..bd5738cc3 100644 --- a/Dalamud/Interface/FontAwesomeExtensions.cs +++ b/Dalamud/Interface/FontAwesomeExtensions.cs @@ -1,30 +1,29 @@ // 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 9435b808f..3c056bf23 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 = 0xF170, - - /// - /// 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 = 0xF5D0, - - /// - /// 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 = 0xF270, - - /// - /// 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 = 0xF100, - - /// - /// 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 = 0xF420, - - /// - /// 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 = 0xF370, - - /// - /// 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 = 0xF060, - - /// - /// 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 = 0xF240, - - /// - /// 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 = 0xF780, - - /// - /// 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 = 0xF850, - - /// - /// 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 = 0xF140, - - /// - /// 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 = 0xF030, - - /// - /// 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 = 0xF150, - - /// - /// 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 = 0xF1F0, - - /// - /// The Font Awesome "centercode" icon unicode character. - /// - Centercode = 0xF380, - - /// - /// 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 = 0xF6C0, - - /// - /// 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 = 0xF080, - - /// - /// The Font Awesome "chart-line" icon unicode character. - /// - ChartLine = 0xF201, - - /// - /// The Font Awesome "chart-pie" icon unicode character. - /// - ChartPie = 0xF200, - - /// - /// 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 = 0xF560, - - /// - /// 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 = 0xF740, - - /// - /// 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 = 0xF4F0, - - /// - /// 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 = 0xF520, - - /// - /// 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 = 0xF210, - - /// - /// The Font Awesome "database" icon unicode character. - /// - Database = 0xF1C0, - - /// - /// 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 = 0xF790, - - /// - /// The Font Awesome "diagnoses" icon unicode character. - /// - Diagnoses = 0xF470, - - /// - /// 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 = 0xF430, - - /// - /// 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 = 0xF0E0, - - /// - /// 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 = 0xF360, - - /// - /// 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 = 0xF070, - - /// - /// 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 = 0xF050, - - /// - /// 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 = 0xF570, - - /// - /// 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 = 0xF0B0, - - /// - /// 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 = 0xF2B0, - - /// - /// 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 = 0xF280, - - /// - /// 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 = 0xF180, - - /// - /// 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 = 0xF260, - - /// - /// 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 = 0xF530, - - /// - /// The Font Awesome "glass-martini" icon unicode character. - /// - GlassMartini = 0xF000, - - /// - /// The Font Awesome "glass-martini-alt" icon unicode character. - /// - GlassMartiniAlt = 0xF57B, - - /// - /// The Font Awesome "glass-whiskey" icon unicode character. - /// - GlassWhiskey = 0xF7A0, - - /// - /// 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 = 0xF450, - - /// - /// 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 = 0xF1A0, - - /// - /// 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 = 0xF580, - - /// - /// 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 = 0xF4C0, - - /// - /// 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 = 0xF8C0, - - /// - /// 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 = 0xF0A0, - - /// - /// 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 = 0xF590, - - /// - /// 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 = 0xF3B0, - - /// - /// 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 = 0xF6F0, - - /// - /// 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 = 0xF810, - - /// - /// 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 = 0xF7B0, - - /// - /// 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 = 0xF3C0, - - /// - /// 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 = 0xF0D0, - - /// - /// 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 = 0xF5A0, - - /// - /// 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 = 0xF2E0, - - /// - /// 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 = 0xF130, - - /// - /// 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 = 0xF610, - - /// - /// 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 = 0xF3D0, - - /// - /// 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 = 0xF510, - - /// - /// 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 = 0xF700, - - /// - /// 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 = 0xF540, - - /// - /// 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 = 0xF1B0, - - /// - /// 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 = 0xF2A0, - - /// - /// 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 = 0xF5B0, - - /// - /// 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 = 0xF3E0, - - /// - /// 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 = 0xF1D0, - - /// - /// 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 = 0xF7C0, - - /// - /// 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 = 0xF010, - - /// - /// 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 = 0xF1E0, - - /// - /// 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 = 0xF290, - - /// - /// 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 = 0xF7D0, - - /// - /// 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 = 0xF160, - - /// - /// 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 = 0xF110, - - /// - /// 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 = 0xF5C0, - - /// - /// 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 = 0xF550, - - /// - /// 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 = 0xF490, - - /// - /// 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 = 0xF120, - - /// - /// 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 = 0xF630, - - /// - /// 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 = 0xF6A0, - - /// - /// 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 = 0xF7E0, - - /// - /// 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 = 0xF500, - - /// - /// 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 = 0xF0F0, - - /// - /// 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 = 0xF0C0, - - /// - /// 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 = 0xF410, - - /// - /// The Font Awesome "window-maximize" icon unicode character. - /// - WindowMaximize = 0xF2D0, - - /// - /// 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 = 0xF730, - - /// - /// 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 = 0xF840, - - /// - /// 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 = 0xF170, + + /// + /// 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 = 0xF5D0, + + /// + /// 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 = 0xF270, + + /// + /// 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 = 0xF100, + + /// + /// 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 = 0xF420, + + /// + /// 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 = 0xF370, + + /// + /// 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 = 0xF060, + + /// + /// 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 = 0xF240, + + /// + /// 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 = 0xF780, + + /// + /// 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 = 0xF850, + + /// + /// 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 = 0xF140, + + /// + /// 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 = 0xF030, + + /// + /// 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 = 0xF150, + + /// + /// 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 = 0xF1F0, + + /// + /// The Font Awesome "centercode" icon unicode character. + /// + Centercode = 0xF380, + + /// + /// 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 = 0xF6C0, + + /// + /// 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 = 0xF080, + + /// + /// The Font Awesome "chart-line" icon unicode character. + /// + ChartLine = 0xF201, + + /// + /// The Font Awesome "chart-pie" icon unicode character. + /// + ChartPie = 0xF200, + + /// + /// 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 = 0xF560, + + /// + /// 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 = 0xF740, + + /// + /// 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 = 0xF4F0, + + /// + /// 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 = 0xF520, + + /// + /// 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 = 0xF210, + + /// + /// The Font Awesome "database" icon unicode character. + /// + Database = 0xF1C0, + + /// + /// 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 = 0xF790, + + /// + /// The Font Awesome "diagnoses" icon unicode character. + /// + Diagnoses = 0xF470, + + /// + /// 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 = 0xF430, + + /// + /// 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 = 0xF0E0, + + /// + /// 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 = 0xF360, + + /// + /// 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 = 0xF070, + + /// + /// 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 = 0xF050, + + /// + /// 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 = 0xF570, + + /// + /// 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 = 0xF0B0, + + /// + /// 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 = 0xF2B0, + + /// + /// 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 = 0xF280, + + /// + /// 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 = 0xF180, + + /// + /// 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 = 0xF260, + + /// + /// 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 = 0xF530, + + /// + /// The Font Awesome "glass-martini" icon unicode character. + /// + GlassMartini = 0xF000, + + /// + /// The Font Awesome "glass-martini-alt" icon unicode character. + /// + GlassMartiniAlt = 0xF57B, + + /// + /// The Font Awesome "glass-whiskey" icon unicode character. + /// + GlassWhiskey = 0xF7A0, + + /// + /// 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 = 0xF450, + + /// + /// 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 = 0xF1A0, + + /// + /// 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 = 0xF580, + + /// + /// 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 = 0xF4C0, + + /// + /// 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 = 0xF8C0, + + /// + /// 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 = 0xF0A0, + + /// + /// 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 = 0xF590, + + /// + /// 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 = 0xF3B0, + + /// + /// 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 = 0xF6F0, + + /// + /// 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 = 0xF810, + + /// + /// 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 = 0xF7B0, + + /// + /// 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 = 0xF3C0, + + /// + /// 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 = 0xF0D0, + + /// + /// 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 = 0xF5A0, + + /// + /// 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 = 0xF2E0, + + /// + /// 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 = 0xF130, + + /// + /// 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 = 0xF610, + + /// + /// 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 = 0xF3D0, + + /// + /// 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 = 0xF510, + + /// + /// 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 = 0xF700, + + /// + /// 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 = 0xF540, + + /// + /// 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 = 0xF1B0, + + /// + /// 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 = 0xF2A0, + + /// + /// 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 = 0xF5B0, + + /// + /// 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 = 0xF3E0, + + /// + /// 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 = 0xF1D0, + + /// + /// 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 = 0xF7C0, + + /// + /// 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 = 0xF010, + + /// + /// 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 = 0xF1E0, + + /// + /// 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 = 0xF290, + + /// + /// 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 = 0xF7D0, + + /// + /// 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 = 0xF160, + + /// + /// 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 = 0xF110, + + /// + /// 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 = 0xF5C0, + + /// + /// 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 = 0xF550, + + /// + /// 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 = 0xF490, + + /// + /// 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 = 0xF120, + + /// + /// 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 = 0xF630, + + /// + /// 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 = 0xF6A0, + + /// + /// 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 = 0xF7E0, + + /// + /// 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 = 0xF500, + + /// + /// 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 = 0xF0F0, + + /// + /// 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 = 0xF0C0, + + /// + /// 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 = 0xF410, + + /// + /// The Font Awesome "window-maximize" icon unicode character. + /// + WindowMaximize = 0xF2D0, + + /// + /// 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 = 0xF730, + + /// + /// 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 = 0xF840, + + /// + /// 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/GameFonts/FdtReader.cs b/Dalamud/Interface/GameFonts/FdtReader.cs index 167cacf2d..a68caba94 100644 --- a/Dalamud/Interface/GameFonts/FdtReader.cs +++ b/Dalamud/Interface/GameFonts/FdtReader.cs @@ -2,426 +2,425 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Parses a game font file. +/// +public class FdtReader { /// - /// Parses a game font file. + /// Initializes a new instance of the class. /// - public class FdtReader + /// Content of a FDT file. + public FdtReader(byte[] data) { - /// - /// Initializes a new instance of the class. - /// - /// Content of a FDT file. - public FdtReader(byte[] data) - { - this.FileHeader = StructureFromByteArray(data, 0); - this.FontHeader = StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset); - this.KerningHeader = StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset); + this.FileHeader = StructureFromByteArray(data, 0); + this.FontHeader = StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset); + this.KerningHeader = StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset); - for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++) - this.Glyphs.Add(StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i))); + for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++) + this.Glyphs.Add(StructureFromByteArray(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i))); - for (int i = 0, i_ = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < i_; i++) - this.Distances.Add(StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i))); - } + for (int i = 0, i_ = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < i_; i++) + this.Distances.Add(StructureFromByteArray(data, this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf() + (Marshal.SizeOf() * i))); + } - /// - /// Gets the header of this file. - /// - public FdtHeader FileHeader { get; init; } + /// + /// Gets the header of this file. + /// + public FdtHeader FileHeader { get; init; } - /// - /// Gets the font header of this file. - /// - public FontTableHeader FontHeader { get; init; } + /// + /// Gets the font header of this file. + /// + public FontTableHeader FontHeader { get; init; } - /// - /// Gets the kerning table header of this file. - /// - public KerningTableHeader KerningHeader { get; init; } + /// + /// Gets the kerning table header of this file. + /// + public KerningTableHeader KerningHeader { get; init; } - /// - /// Gets all the glyphs defined in this file. - /// - public List Glyphs { get; init; } = new(); + /// + /// Gets all the glyphs defined in this file. + /// + public List Glyphs { get; init; } = new(); - /// - /// Gets all the kerning entries defined in this file. - /// - public List Distances { get; init; } = new(); + /// + /// Gets all the kerning entries defined in this file. + /// + public List Distances { get; init; } = new(); - /// - /// Finds glyph definition for corresponding codepoint. - /// - /// Unicode codepoint (UTF-32 value). - /// Corresponding FontTableEntry, or null if not found. - public FontTableEntry? FindGlyph(int codepoint) - { - var i = this.Glyphs.BinarySearch(new FontTableEntry { CharUtf8 = CodePointToUtf8Int32(codepoint) }); - if (i < 0 || i == this.Glyphs.Count) - return null; - return this.Glyphs[i]; - } + /// + /// Finds glyph definition for corresponding codepoint. + /// + /// Unicode codepoint (UTF-32 value). + /// Corresponding FontTableEntry, or null if not found. + public FontTableEntry? FindGlyph(int codepoint) + { + var i = this.Glyphs.BinarySearch(new FontTableEntry { CharUtf8 = CodePointToUtf8Int32(codepoint) }); + if (i < 0 || i == this.Glyphs.Count) + return null; + return this.Glyphs[i]; + } - /// - /// Returns glyph definition for corresponding codepoint. - /// - /// Unicode codepoint (UTF-32 value). - /// Corresponding FontTableEntry, or that of a fallback character. - public FontTableEntry GetGlyph(int codepoint) - { - return (this.FindGlyph(codepoint) + /// + /// Returns glyph definition for corresponding codepoint. + /// + /// Unicode codepoint (UTF-32 value). + /// Corresponding FontTableEntry, or that of a fallback character. + public FontTableEntry GetGlyph(int codepoint) + { + return (this.FindGlyph(codepoint) ?? this.FindGlyph('〓') ?? this.FindGlyph('?') ?? this.FindGlyph('='))!.Value; + } + + /// + /// Returns distance adjustment between two adjacent characters. + /// + /// Left character. + /// Right character. + /// Supposed distance adjustment between given characters. + public int GetDistance(int codepoint1, int codepoint2) + { + var i = this.Distances.BinarySearch(new KerningTableEntry { LeftUtf8 = CodePointToUtf8Int32(codepoint1), RightUtf8 = CodePointToUtf8Int32(codepoint2) }); + if (i < 0 || i == this.Distances.Count) + return 0; + return this.Distances[i].RightOffset; + } + + private static unsafe T StructureFromByteArray(byte[] data, int offset) + { + var len = Marshal.SizeOf(); + if (offset + len > data.Length) + throw new Exception("Data too short"); + + fixed (byte* ptr = data) + return Marshal.PtrToStructure(new(ptr + offset)); + } + + private static int CodePointToUtf8Int32(int codepoint) + { + if (codepoint <= 0x7F) + { + return codepoint; } + else if (codepoint <= 0x7FF) + { + return ((0xC0 | (codepoint >> 6)) << 8) + | ((0x80 | ((codepoint >> 0) & 0x3F)) << 0); + } + else if (codepoint <= 0xFFFF) + { + return ((0xE0 | (codepoint >> 12)) << 16) + | ((0x80 | ((codepoint >> 6) & 0x3F)) << 8) + | ((0x80 | ((codepoint >> 0) & 0x3F)) << 0); + } + else if (codepoint <= 0x10FFFF) + { + return ((0xF0 | (codepoint >> 18)) << 24) + | ((0x80 | ((codepoint >> 12) & 0x3F)) << 16) + | ((0x80 | ((codepoint >> 6) & 0x3F)) << 8) + | ((0x80 | ((codepoint >> 0) & 0x3F)) << 0); + } + else + { + return 0xFFFE; + } + } + + private static int Utf8Uint32ToCodePoint(int n) + { + if ((n & 0xFFFFFF80) == 0) + { + return n & 0x7F; + } + else if ((n & 0xFFFFE0C0) == 0xC080) + { + return + (((n >> 0x08) & 0x1F) << 6) | + (((n >> 0x00) & 0x3F) << 0); + } + else if ((n & 0xF0C0C0) == 0xE08080) + { + return + (((n >> 0x10) & 0x0F) << 12) | + (((n >> 0x08) & 0x3F) << 6) | + (((n >> 0x00) & 0x3F) << 0); + } + else if ((n & 0xF8C0C0C0) == 0xF0808080) + { + return + (((n >> 0x18) & 0x07) << 18) | + (((n >> 0x10) & 0x3F) << 12) | + (((n >> 0x08) & 0x3F) << 6) | + (((n >> 0x00) & 0x3F) << 0); + } + else + { + return 0xFFFF; // Guaranteed non-unicode + } + } + + /// + /// Header of game font file format. + /// + [StructLayout(LayoutKind.Sequential)] + public unsafe struct FdtHeader + { + /// + /// Signature: "fcsv". + /// + public fixed byte Signature[8]; /// - /// Returns distance adjustment between two adjacent characters. + /// Offset to FontTableHeader. /// - /// Left character. - /// Right character. - /// Supposed distance adjustment between given characters. - public int GetDistance(int codepoint1, int codepoint2) + public int FontTableHeaderOffset; + + /// + /// Offset to KerningTableHeader. + /// + public int KerningTableHeaderOffset; + + /// + /// Unused/unknown. + /// + public fixed byte Padding[0x10]; + } + + /// + /// Header of glyph table. + /// + [StructLayout(LayoutKind.Sequential)] + public unsafe struct FontTableHeader + { + /// + /// Signature: "fthd". + /// + public fixed byte Signature[4]; + + /// + /// Number of glyphs defined in this file. + /// + public int FontTableEntryCount; + + /// + /// Number of kerning informations defined in this file. + /// + public int KerningTableEntryCount; + + /// + /// Unused/unknown. + /// + public fixed byte Padding[0x04]; + + /// + /// Width of backing texture. + /// + public ushort TextureWidth; + + /// + /// Height of backing texture. + /// + public ushort TextureHeight; + + /// + /// Size of the font defined from this file, in points unit. + /// + public float Size; + + /// + /// Line height of the font defined forom this file, in pixels unit. + /// + public int LineHeight; + + /// + /// Ascent of the font defined from this file, in pixels unit. + /// + public int Ascent; + + /// + /// Gets descent of the font defined from this file, in pixels unit. + /// + public int Descent => this.LineHeight - this.Ascent; + } + + /// + /// Glyph table entry. + /// + [StructLayout(LayoutKind.Sequential)] + public unsafe struct FontTableEntry : IComparable + { + /// + /// Mapping of texture channel index to byte index. + /// + public static readonly int[] TextureChannelOrder = { 2, 1, 0, 3 }; + + /// + /// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian. + /// + public int CharUtf8; + + /// + /// Integer representation of a Shift_JIS character in reverse order, read in little endian. + /// + public ushort CharSjis; + + /// + /// Index of backing texture. + /// + public ushort TextureIndex; + + /// + /// Horizontal offset of glyph image in the backing texture. + /// + public ushort TextureOffsetX; + + /// + /// Vertical offset of glyph image in the backing texture. + /// + public ushort TextureOffsetY; + + /// + /// Bounding width of this glyph. + /// + public byte BoundingWidth; + + /// + /// Bounding height of this glyph. + /// + public byte BoundingHeight; + + /// + /// Distance adjustment for drawing next character. + /// + public sbyte NextOffsetX; + + /// + /// Distance adjustment for drawing current character. + /// + public sbyte CurrentOffsetY; + + /// + /// Gets the index of the file among all the backing texture files. + /// + public int TextureFileIndex => this.TextureIndex / 4; + + /// + /// Gets the channel index in the backing texture file. + /// + public int TextureChannelIndex => this.TextureIndex % 4; + + /// + /// Gets the byte index in a multichannel pixel corresponding to the channel. + /// + public int TextureChannelByteIndex => TextureChannelOrder[this.TextureChannelIndex]; + + /// + /// Gets the advance width of this character. + /// + public int AdvanceWidth => this.BoundingWidth + this.NextOffsetX; + + /// + /// Gets the Unicode codepoint of the character for this entry in int type. + /// + public int CharInt => Utf8Uint32ToCodePoint(this.CharUtf8); + + /// + /// Gets the Unicode codepoint of the character for this entry in char type. + /// + public char Char => (char)Utf8Uint32ToCodePoint(this.CharUtf8); + + /// + public int CompareTo(FontTableEntry other) { - var i = this.Distances.BinarySearch(new KerningTableEntry { LeftUtf8 = CodePointToUtf8Int32(codepoint1), RightUtf8 = CodePointToUtf8Int32(codepoint2) }); - if (i < 0 || i == this.Distances.Count) - return 0; - return this.Distances[i].RightOffset; + return this.CharUtf8 - other.CharUtf8; } + } - private static unsafe T StructureFromByteArray(byte[] data, int offset) + /// + /// Header of kerning table. + /// + [StructLayout(LayoutKind.Sequential)] + public unsafe struct KerningTableHeader + { + /// + /// Signature: "knhd". + /// + public fixed byte Signature[4]; + + /// + /// Number of kerning entries in this table. + /// + public int Count; + + /// + /// Unused/unknown. + /// + public fixed byte Padding[0x08]; + } + + /// + /// Kerning table entry. + /// + [StructLayout(LayoutKind.Sequential)] + public unsafe struct KerningTableEntry : IComparable + { + /// + /// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the left character. + /// + public int LeftUtf8; + + /// + /// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the right character. + /// + public int RightUtf8; + + /// + /// Integer representation of a Shift_JIS character in reverse order, read in little endian, for the left character. + /// + public ushort LeftSjis; + + /// + /// Integer representation of a Shift_JIS character in reverse order, read in little endian, for the right character. + /// + public ushort RightSjis; + + /// + /// Horizontal offset adjustment for the right character. + /// + public int RightOffset; + + /// + /// Gets the Unicode codepoint of the character for this entry in int type. + /// + public int LeftInt => Utf8Uint32ToCodePoint(this.LeftUtf8); + + /// + /// Gets the Unicode codepoint of the character for this entry in char type. + /// + public char Left => (char)Utf8Uint32ToCodePoint(this.LeftUtf8); + + /// + /// Gets the Unicode codepoint of the character for this entry in int type. + /// + public int RightInt => Utf8Uint32ToCodePoint(this.RightUtf8); + + /// + /// Gets the Unicode codepoint of the character for this entry in char type. + /// + public char Right => (char)Utf8Uint32ToCodePoint(this.RightUtf8); + + /// + public int CompareTo(KerningTableEntry other) { - var len = Marshal.SizeOf(); - if (offset + len > data.Length) - throw new Exception("Data too short"); - - fixed (byte* ptr = data) - return Marshal.PtrToStructure(new(ptr + offset)); - } - - private static int CodePointToUtf8Int32(int codepoint) - { - if (codepoint <= 0x7F) - { - return codepoint; - } - else if (codepoint <= 0x7FF) - { - return ((0xC0 | (codepoint >> 6)) << 8) - | ((0x80 | ((codepoint >> 0) & 0x3F)) << 0); - } - else if (codepoint <= 0xFFFF) - { - return ((0xE0 | (codepoint >> 12)) << 16) - | ((0x80 | ((codepoint >> 6) & 0x3F)) << 8) - | ((0x80 | ((codepoint >> 0) & 0x3F)) << 0); - } - else if (codepoint <= 0x10FFFF) - { - return ((0xF0 | (codepoint >> 18)) << 24) - | ((0x80 | ((codepoint >> 12) & 0x3F)) << 16) - | ((0x80 | ((codepoint >> 6) & 0x3F)) << 8) - | ((0x80 | ((codepoint >> 0) & 0x3F)) << 0); - } + if (this.LeftUtf8 == other.LeftUtf8) + return this.RightUtf8 - other.RightUtf8; else - { - return 0xFFFE; - } - } - - private static int Utf8Uint32ToCodePoint(int n) - { - if ((n & 0xFFFFFF80) == 0) - { - return n & 0x7F; - } - else if ((n & 0xFFFFE0C0) == 0xC080) - { - return - (((n >> 0x08) & 0x1F) << 6) | - (((n >> 0x00) & 0x3F) << 0); - } - else if ((n & 0xF0C0C0) == 0xE08080) - { - return - (((n >> 0x10) & 0x0F) << 12) | - (((n >> 0x08) & 0x3F) << 6) | - (((n >> 0x00) & 0x3F) << 0); - } - else if ((n & 0xF8C0C0C0) == 0xF0808080) - { - return - (((n >> 0x18) & 0x07) << 18) | - (((n >> 0x10) & 0x3F) << 12) | - (((n >> 0x08) & 0x3F) << 6) | - (((n >> 0x00) & 0x3F) << 0); - } - else - { - return 0xFFFF; // Guaranteed non-unicode - } - } - - /// - /// Header of game font file format. - /// - [StructLayout(LayoutKind.Sequential)] - public unsafe struct FdtHeader - { - /// - /// Signature: "fcsv". - /// - public fixed byte Signature[8]; - - /// - /// Offset to FontTableHeader. - /// - public int FontTableHeaderOffset; - - /// - /// Offset to KerningTableHeader. - /// - public int KerningTableHeaderOffset; - - /// - /// Unused/unknown. - /// - public fixed byte Padding[0x10]; - } - - /// - /// Header of glyph table. - /// - [StructLayout(LayoutKind.Sequential)] - public unsafe struct FontTableHeader - { - /// - /// Signature: "fthd". - /// - public fixed byte Signature[4]; - - /// - /// Number of glyphs defined in this file. - /// - public int FontTableEntryCount; - - /// - /// Number of kerning informations defined in this file. - /// - public int KerningTableEntryCount; - - /// - /// Unused/unknown. - /// - public fixed byte Padding[0x04]; - - /// - /// Width of backing texture. - /// - public ushort TextureWidth; - - /// - /// Height of backing texture. - /// - public ushort TextureHeight; - - /// - /// Size of the font defined from this file, in points unit. - /// - public float Size; - - /// - /// Line height of the font defined forom this file, in pixels unit. - /// - public int LineHeight; - - /// - /// Ascent of the font defined from this file, in pixels unit. - /// - public int Ascent; - - /// - /// Gets descent of the font defined from this file, in pixels unit. - /// - public int Descent => this.LineHeight - this.Ascent; - } - - /// - /// Glyph table entry. - /// - [StructLayout(LayoutKind.Sequential)] - public unsafe struct FontTableEntry : IComparable - { - /// - /// Mapping of texture channel index to byte index. - /// - public static readonly int[] TextureChannelOrder = { 2, 1, 0, 3 }; - - /// - /// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian. - /// - public int CharUtf8; - - /// - /// Integer representation of a Shift_JIS character in reverse order, read in little endian. - /// - public ushort CharSjis; - - /// - /// Index of backing texture. - /// - public ushort TextureIndex; - - /// - /// Horizontal offset of glyph image in the backing texture. - /// - public ushort TextureOffsetX; - - /// - /// Vertical offset of glyph image in the backing texture. - /// - public ushort TextureOffsetY; - - /// - /// Bounding width of this glyph. - /// - public byte BoundingWidth; - - /// - /// Bounding height of this glyph. - /// - public byte BoundingHeight; - - /// - /// Distance adjustment for drawing next character. - /// - public sbyte NextOffsetX; - - /// - /// Distance adjustment for drawing current character. - /// - public sbyte CurrentOffsetY; - - /// - /// Gets the index of the file among all the backing texture files. - /// - public int TextureFileIndex => this.TextureIndex / 4; - - /// - /// Gets the channel index in the backing texture file. - /// - public int TextureChannelIndex => this.TextureIndex % 4; - - /// - /// Gets the byte index in a multichannel pixel corresponding to the channel. - /// - public int TextureChannelByteIndex => TextureChannelOrder[this.TextureChannelIndex]; - - /// - /// Gets the advance width of this character. - /// - public int AdvanceWidth => this.BoundingWidth + this.NextOffsetX; - - /// - /// Gets the Unicode codepoint of the character for this entry in int type. - /// - public int CharInt => Utf8Uint32ToCodePoint(this.CharUtf8); - - /// - /// Gets the Unicode codepoint of the character for this entry in char type. - /// - public char Char => (char)Utf8Uint32ToCodePoint(this.CharUtf8); - - /// - public int CompareTo(FontTableEntry other) - { - return this.CharUtf8 - other.CharUtf8; - } - } - - /// - /// Header of kerning table. - /// - [StructLayout(LayoutKind.Sequential)] - public unsafe struct KerningTableHeader - { - /// - /// Signature: "knhd". - /// - public fixed byte Signature[4]; - - /// - /// Number of kerning entries in this table. - /// - public int Count; - - /// - /// Unused/unknown. - /// - public fixed byte Padding[0x08]; - } - - /// - /// Kerning table entry. - /// - [StructLayout(LayoutKind.Sequential)] - public unsafe struct KerningTableEntry : IComparable - { - /// - /// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the left character. - /// - public int LeftUtf8; - - /// - /// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the right character. - /// - public int RightUtf8; - - /// - /// Integer representation of a Shift_JIS character in reverse order, read in little endian, for the left character. - /// - public ushort LeftSjis; - - /// - /// Integer representation of a Shift_JIS character in reverse order, read in little endian, for the right character. - /// - public ushort RightSjis; - - /// - /// Horizontal offset adjustment for the right character. - /// - public int RightOffset; - - /// - /// Gets the Unicode codepoint of the character for this entry in int type. - /// - public int LeftInt => Utf8Uint32ToCodePoint(this.LeftUtf8); - - /// - /// Gets the Unicode codepoint of the character for this entry in char type. - /// - public char Left => (char)Utf8Uint32ToCodePoint(this.LeftUtf8); - - /// - /// Gets the Unicode codepoint of the character for this entry in int type. - /// - public int RightInt => Utf8Uint32ToCodePoint(this.RightUtf8); - - /// - /// Gets the Unicode codepoint of the character for this entry in char type. - /// - public char Right => (char)Utf8Uint32ToCodePoint(this.RightUtf8); - - /// - public int CompareTo(KerningTableEntry other) - { - if (this.LeftUtf8 == other.LeftUtf8) - return this.RightUtf8 - other.RightUtf8; - else - return this.LeftUtf8 - other.LeftUtf8; - } + return this.LeftUtf8 - other.LeftUtf8; } } } diff --git a/Dalamud/Interface/GameFonts/GameFontFamily.cs b/Dalamud/Interface/GameFonts/GameFontFamily.cs index cb3e84a59..7f5f9a2cc 100644 --- a/Dalamud/Interface/GameFonts/GameFontFamily.cs +++ b/Dalamud/Interface/GameFonts/GameFontFamily.cs @@ -1,43 +1,42 @@ -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Enum of available game font families. +/// +public enum GameFontFamily { /// - /// Enum of available game font families. + /// Placeholder meaning unused. /// - public enum GameFontFamily - { - /// - /// Placeholder meaning unused. - /// - Undefined, + Undefined, - /// - /// Sans-serif fonts used for the whole UI. Contains Japanese characters in addition to Latin characters. - /// - Axis, + /// + /// Sans-serif fonts used for the whole UI. Contains Japanese characters in addition to Latin characters. + /// + Axis, - /// - /// Serif fonts used for job names. Contains Latin characters. - /// - Jupiter, + /// + /// Serif fonts used for job names. Contains Latin characters. + /// + Jupiter, - /// - /// Digit-only serif fonts used for flying texts. Contains numbers. - /// - JupiterNumeric, + /// + /// Digit-only serif fonts used for flying texts. Contains numbers. + /// + JupiterNumeric, - /// - /// Digit-only sans-serif horizontally wide fonts used for HP/MP/IL numbers. - /// - Meidinger, + /// + /// Digit-only sans-serif horizontally wide fonts used for HP/MP/IL numbers. + /// + Meidinger, - /// - /// Sans-serif horizontally wide font used for names of gauges. Contains Latin characters. - /// - MiedingerMid, + /// + /// Sans-serif horizontally wide font used for names of gauges. Contains Latin characters. + /// + MiedingerMid, - /// - /// Sans-serif horizontally narrow font used for addon titles. Contains Latin characters. - /// - TrumpGothic, - } + /// + /// Sans-serif horizontally narrow font used for addon titles. Contains Latin characters. + /// + TrumpGothic, } diff --git a/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs b/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs index 1cbdf210f..dd78baf87 100644 --- a/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs +++ b/Dalamud/Interface/GameFonts/GameFontFamilyAndSize.cs @@ -1,174 +1,173 @@ -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Enum of available game fonts in specific sizes. +/// +public enum GameFontFamilyAndSize : int { /// - /// Enum of available game fonts in specific sizes. + /// Placeholder meaning unused. /// - public enum GameFontFamilyAndSize : int - { - /// - /// Placeholder meaning unused. - /// - Undefined, + Undefined, - /// - /// AXIS (9.6pt) - /// - /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. - /// - Axis96, + /// + /// AXIS (9.6pt) + /// + /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. + /// + Axis96, - /// - /// AXIS (12pt) - /// - /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. - /// - Axis12, + /// + /// AXIS (12pt) + /// + /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. + /// + Axis12, - /// - /// AXIS (14pt) - /// - /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. - /// - Axis14, + /// + /// AXIS (14pt) + /// + /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. + /// + Axis14, - /// - /// AXIS (18pt) - /// - /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. - /// - Axis18, + /// + /// AXIS (18pt) + /// + /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. + /// + Axis18, - /// - /// AXIS (36pt) - /// - /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. - /// - Axis36, + /// + /// AXIS (36pt) + /// + /// Contains Japanese characters in addition to Latin characters. Used in game for the whole UI. + /// + Axis36, - /// - /// Jupiter (16pt) - /// - /// Serif font. Contains mostly ASCII range. Used in game for job names. - /// - Jupiter16, + /// + /// Jupiter (16pt) + /// + /// Serif font. Contains mostly ASCII range. Used in game for job names. + /// + Jupiter16, - /// - /// Jupiter (20pt) - /// - /// Serif font. Contains mostly ASCII range. Used in game for job names. - /// - Jupiter20, + /// + /// Jupiter (20pt) + /// + /// Serif font. Contains mostly ASCII range. Used in game for job names. + /// + Jupiter20, - /// - /// Jupiter (23pt) - /// - /// Serif font. Contains mostly ASCII range. Used in game for job names. - /// - Jupiter23, + /// + /// Jupiter (23pt) + /// + /// Serif font. Contains mostly ASCII range. Used in game for job names. + /// + Jupiter23, - /// - /// Jupiter (45pt) - /// - /// Serif font. Contains mostly numbers. Used in game for flying texts. - /// - Jupiter45, + /// + /// Jupiter (45pt) + /// + /// Serif font. Contains mostly numbers. Used in game for flying texts. + /// + Jupiter45, - /// - /// Jupiter (46pt) - /// - /// Serif font. Contains mostly ASCII range. Used in game for job names. - /// - Jupiter46, + /// + /// Jupiter (46pt) + /// + /// Serif font. Contains mostly ASCII range. Used in game for job names. + /// + Jupiter46, - /// - /// Jupiter (90pt) - /// - /// Serif font. Contains mostly numbers. Used in game for flying texts. - /// - Jupiter90, + /// + /// Jupiter (90pt) + /// + /// Serif font. Contains mostly numbers. Used in game for flying texts. + /// + Jupiter90, - /// - /// Meidinger (16pt) - /// - /// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff. - /// - Meidinger16, + /// + /// Meidinger (16pt) + /// + /// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff. + /// + Meidinger16, - /// - /// Meidinger (20pt) - /// - /// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff. - /// - Meidinger20, + /// + /// Meidinger (20pt) + /// + /// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff. + /// + Meidinger20, - /// - /// Meidinger (40pt) - /// - /// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff. - /// - Meidinger40, + /// + /// Meidinger (40pt) + /// + /// Horizontally wide. Contains mostly numbers. Used in game for HP/MP/IL stuff. + /// + Meidinger40, - /// - /// MiedingerMid (10pt) - /// - /// Horizontally wide. Contains mostly ASCII range. - /// - MiedingerMid10, + /// + /// MiedingerMid (10pt) + /// + /// Horizontally wide. Contains mostly ASCII range. + /// + MiedingerMid10, - /// - /// MiedingerMid (12pt) - /// - /// Horizontally wide. Contains mostly ASCII range. - /// - MiedingerMid12, + /// + /// MiedingerMid (12pt) + /// + /// Horizontally wide. Contains mostly ASCII range. + /// + MiedingerMid12, - /// - /// MiedingerMid (14pt) - /// - /// Horizontally wide. Contains mostly ASCII range. - /// - MiedingerMid14, + /// + /// MiedingerMid (14pt) + /// + /// Horizontally wide. Contains mostly ASCII range. + /// + MiedingerMid14, - /// - /// MiedingerMid (18pt) - /// - /// Horizontally wide. Contains mostly ASCII range. - /// - MiedingerMid18, + /// + /// MiedingerMid (18pt) + /// + /// Horizontally wide. Contains mostly ASCII range. + /// + MiedingerMid18, - /// - /// MiedingerMid (36pt) - /// - /// Horizontally wide. Contains mostly ASCII range. - /// - MiedingerMid36, + /// + /// MiedingerMid (36pt) + /// + /// Horizontally wide. Contains mostly ASCII range. + /// + MiedingerMid36, - /// - /// TrumpGothic (18.4pt) - /// - /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. - /// - TrumpGothic184, + /// + /// TrumpGothic (18.4pt) + /// + /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. + /// + TrumpGothic184, - /// - /// TrumpGothic (23pt) - /// - /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. - /// - TrumpGothic23, + /// + /// TrumpGothic (23pt) + /// + /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. + /// + TrumpGothic23, - /// - /// TrumpGothic (34pt) - /// - /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. - /// - TrumpGothic34, + /// + /// TrumpGothic (34pt) + /// + /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. + /// + TrumpGothic34, - /// - /// TrumpGothic (688pt) - /// - /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. - /// - TrumpGothic68, - } + /// + /// TrumpGothic (688pt) + /// + /// Horizontally narrow. Contains mostly ASCII range. Used for addon titles. + /// + TrumpGothic68, } diff --git a/Dalamud/Interface/GameFonts/GameFontHandle.cs b/Dalamud/Interface/GameFonts/GameFontHandle.cs index e2fa1d941..d71e725c5 100644 --- a/Dalamud/Interface/GameFonts/GameFontHandle.cs +++ b/Dalamud/Interface/GameFonts/GameFontHandle.cs @@ -3,113 +3,112 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Prepare and keep game font loaded for use in OnDraw. +/// +public class GameFontHandle : IDisposable { + private readonly GameFontManager manager; + private readonly GameFontStyle fontStyle; + /// - /// Prepare and keep game font loaded for use in OnDraw. + /// Initializes a new instance of the class. /// - public class GameFontHandle : IDisposable + /// GameFontManager instance. + /// Font to use. + internal GameFontHandle(GameFontManager manager, GameFontStyle font) { - private readonly GameFontManager manager; - private readonly GameFontStyle fontStyle; + this.manager = manager; + this.fontStyle = font; + } - /// - /// Initializes a new instance of the class. - /// - /// GameFontManager instance. - /// Font to use. - internal GameFontHandle(GameFontManager manager, GameFontStyle font) - { - this.manager = manager; - this.fontStyle = font; - } + /// + /// Gets the font style. + /// + public GameFontStyle Style => this.fontStyle; - /// - /// Gets the font style. - /// - public GameFontStyle Style => this.fontStyle; - - /// - /// Gets a value indicating whether this font is ready for use. - /// - public bool Available - { - get - { - unsafe - { - return this.manager.GetFont(this.fontStyle).GetValueOrDefault(null).NativePtr != null; - } - } - } - - /// - /// Gets the font. - /// - public ImFontPtr ImFont => this.manager.GetFont(this.fontStyle).Value; - - /// - /// Gets the FdtReader. - /// - public FdtReader FdtReader => this.manager.GetFdtReader(this.fontStyle.FamilyAndSize); - - /// - /// Creates a new GameFontLayoutPlan.Builder. - /// - /// Text. - /// A new builder for GameFontLayoutPlan. - public GameFontLayoutPlan.Builder LayoutBuilder(string text) - { - return new GameFontLayoutPlan.Builder(this.ImFont, this.FdtReader, text); - } - - /// - public void Dispose() => this.manager.DecreaseFontRef(this.fontStyle); - - /// - /// Draws text. - /// - /// Text to draw. - public void Text(string text) - { - if (!this.Available) - { - ImGui.TextUnformatted(text); - } - else - { - var pos = ImGui.GetWindowPos() + ImGui.GetCursorPos(); - pos.X -= ImGui.GetScrollX(); - pos.Y -= ImGui.GetScrollY(); - - var layout = this.LayoutBuilder(text).Build(); - layout.Draw(ImGui.GetWindowDrawList(), pos, ImGui.GetColorU32(ImGuiCol.Text)); - ImGui.Dummy(new Vector2(layout.Width, layout.Height)); - } - } - - /// - /// Draws text in given color. - /// - /// Color. - /// Text to draw. - public void TextColored(Vector4 col, string text) - { - ImGui.PushStyleColor(ImGuiCol.Text, col); - this.Text(text); - ImGui.PopStyleColor(); - } - - /// - /// Draws disabled text. - /// - /// Text to draw. - public void TextDisabled(string text) + /// + /// Gets a value indicating whether this font is ready for use. + /// + public bool Available + { + get { unsafe { - this.TextColored(*ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled), text); + return this.manager.GetFont(this.fontStyle).GetValueOrDefault(null).NativePtr != null; } } } + + /// + /// Gets the font. + /// + public ImFontPtr ImFont => this.manager.GetFont(this.fontStyle).Value; + + /// + /// Gets the FdtReader. + /// + public FdtReader FdtReader => this.manager.GetFdtReader(this.fontStyle.FamilyAndSize); + + /// + /// Creates a new GameFontLayoutPlan.Builder. + /// + /// Text. + /// A new builder for GameFontLayoutPlan. + public GameFontLayoutPlan.Builder LayoutBuilder(string text) + { + return new GameFontLayoutPlan.Builder(this.ImFont, this.FdtReader, text); + } + + /// + public void Dispose() => this.manager.DecreaseFontRef(this.fontStyle); + + /// + /// Draws text. + /// + /// Text to draw. + public void Text(string text) + { + if (!this.Available) + { + ImGui.TextUnformatted(text); + } + else + { + var pos = ImGui.GetWindowPos() + ImGui.GetCursorPos(); + pos.X -= ImGui.GetScrollX(); + pos.Y -= ImGui.GetScrollY(); + + var layout = this.LayoutBuilder(text).Build(); + layout.Draw(ImGui.GetWindowDrawList(), pos, ImGui.GetColorU32(ImGuiCol.Text)); + ImGui.Dummy(new Vector2(layout.Width, layout.Height)); + } + } + + /// + /// Draws text in given color. + /// + /// Color. + /// Text to draw. + public void TextColored(Vector4 col, string text) + { + ImGui.PushStyleColor(ImGuiCol.Text, col); + this.Text(text); + ImGui.PopStyleColor(); + } + + /// + /// Draws disabled text. + /// + /// Text to draw. + public void TextDisabled(string text) + { + unsafe + { + this.TextColored(*ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled), text); + } + } } diff --git a/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs b/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs index 93fe5ab87..587c0dbe3 100644 --- a/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs +++ b/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs @@ -4,407 +4,406 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Plan on how glyphs will be rendered. +/// +public class GameFontLayoutPlan { /// - /// Plan on how glyphs will be rendered. + /// Horizontal alignment. /// - public class GameFontLayoutPlan + public enum HorizontalAlignment { /// - /// Horizontal alignment. + /// Align to left. /// - public enum HorizontalAlignment + Left, + + /// + /// Align to center. + /// + Center, + + /// + /// Align to right. + /// + Right, + } + + /// + /// Gets the associated ImFontPtr. + /// + public ImFontPtr ImFontPtr { get; internal set; } + + /// + /// Gets the size in points of the text. + /// + public float Size { get; internal set; } + + /// + /// Gets the x offset of the leftmost glyph. + /// + public float X { get; internal set; } + + /// + /// Gets the width of the text. + /// + public float Width { get; internal set; } + + /// + /// Gets the height of the text. + /// + public float Height { get; internal set; } + + /// + /// Gets the list of plannen elements. + /// + public IList Elements { get; internal set; } + + /// + /// Draws font to ImGui. + /// + /// Target ImDrawList. + /// Position. + /// Color. + public void Draw(ImDrawListPtr drawListPtr, Vector2 pos, uint col) + { + foreach (var element in this.Elements) { - /// - /// Align to left. - /// - Left, + if (element.IsControl) + continue; - /// - /// Align to center. - /// - Center, - - /// - /// Align to right. - /// - Right, + this.ImFontPtr.RenderChar( + drawListPtr, + this.Size, + new Vector2( + this.X + pos.X + element.X, + pos.Y + element.Y), + col, + element.Glyph.Char); } + } + /// + /// Plan on how each glyph will be rendered. + /// + public class Element + { /// - /// Gets the associated ImFontPtr. + /// Gets the original codepoint. /// - public ImFontPtr ImFontPtr { get; internal set; } + public int Codepoint { get; init; } /// - /// Gets the size in points of the text. + /// Gets the corresponding or fallback glyph. /// - public float Size { get; internal set; } + public FdtReader.FontTableEntry Glyph { get; init; } /// - /// Gets the x offset of the leftmost glyph. + /// Gets the X offset of this glyph. /// public float X { get; internal set; } /// - /// Gets the width of the text. + /// Gets the Y offset of this glyph. /// - public float Width { get; internal set; } + public float Y { get; internal set; } /// - /// Gets the height of the text. + /// Gets a value indicating whether whether this codepoint is a control character. /// - public float Height { get; internal set; } - - /// - /// Gets the list of plannen elements. - /// - public IList Elements { get; internal set; } - - /// - /// Draws font to ImGui. - /// - /// Target ImDrawList. - /// Position. - /// Color. - public void Draw(ImDrawListPtr drawListPtr, Vector2 pos, uint col) + public bool IsControl { - foreach (var element in this.Elements) + get { - if (element.IsControl) - continue; - - this.ImFontPtr.RenderChar( - drawListPtr, - this.Size, - new Vector2( - this.X + pos.X + element.X, - pos.Y + element.Y), - col, - element.Glyph.Char); + return this.Codepoint < 0x10000 && char.IsControl((char)this.Codepoint); } } /// - /// Plan on how each glyph will be rendered. + /// Gets a value indicating whether whether this codepoint is a space. /// - public class Element + public bool IsSpace { - /// - /// Gets the original codepoint. - /// - public int Codepoint { get; init; } - - /// - /// Gets the corresponding or fallback glyph. - /// - public FdtReader.FontTableEntry Glyph { get; init; } - - /// - /// Gets the X offset of this glyph. - /// - public float X { get; internal set; } - - /// - /// Gets the Y offset of this glyph. - /// - public float Y { get; internal set; } - - /// - /// Gets a value indicating whether whether this codepoint is a control character. - /// - public bool IsControl + get { - get - { - return this.Codepoint < 0x10000 && char.IsControl((char)this.Codepoint); - } - } - - /// - /// Gets a value indicating whether whether this codepoint is a space. - /// - public bool IsSpace - { - get - { - return this.Codepoint < 0x10000 && char.IsWhiteSpace((char)this.Codepoint); - } - } - - /// - /// Gets a value indicating whether whether this codepoint is a line break character. - /// - public bool IsLineBreak - { - get - { - return this.Codepoint == '\n' || this.Codepoint == '\r'; - } - } - - /// - /// Gets a value indicating whether whether this codepoint is a chinese character. - /// - public bool IsChineseCharacter - { - get - { - // CJK Symbols and Punctuation(〇) - if (this.Codepoint >= 0x3007 && this.Codepoint <= 0x3007) - return true; - - // CJK Unified Ideographs Extension A - if (this.Codepoint >= 0x3400 && this.Codepoint <= 0x4DBF) - return true; - - // CJK Unified Ideographs - if (this.Codepoint >= 0x4E00 && this.Codepoint <= 0x9FFF) - return true; - - // CJK Unified Ideographs Extension B - if (this.Codepoint >= 0x20000 && this.Codepoint <= 0x2A6DF) - return true; - - // CJK Unified Ideographs Extension C - if (this.Codepoint >= 0x2A700 && this.Codepoint <= 0x2B73F) - return true; - - // CJK Unified Ideographs Extension D - if (this.Codepoint >= 0x2B740 && this.Codepoint <= 0x2B81F) - return true; - - // CJK Unified Ideographs Extension E - if (this.Codepoint >= 0x2B820 && this.Codepoint <= 0x2CEAF) - return true; - - // CJK Unified Ideographs Extension F - if (this.Codepoint >= 0x2CEB0 && this.Codepoint <= 0x2EBEF) - return true; - - return false; - } - } - - /// - /// Gets a value indicating whether whether this codepoint is a good position to break word after. - /// - public bool IsWordBreakPoint - { - get - { - if (this.IsChineseCharacter) - return true; - - if (this.Codepoint >= 0x10000) - return false; - - // TODO: Whatever - switch (char.GetUnicodeCategory((char)this.Codepoint)) - { - case System.Globalization.UnicodeCategory.SpaceSeparator: - case System.Globalization.UnicodeCategory.LineSeparator: - case System.Globalization.UnicodeCategory.ParagraphSeparator: - case System.Globalization.UnicodeCategory.Control: - case System.Globalization.UnicodeCategory.Format: - case System.Globalization.UnicodeCategory.Surrogate: - case System.Globalization.UnicodeCategory.PrivateUse: - case System.Globalization.UnicodeCategory.ConnectorPunctuation: - case System.Globalization.UnicodeCategory.DashPunctuation: - case System.Globalization.UnicodeCategory.OpenPunctuation: - case System.Globalization.UnicodeCategory.ClosePunctuation: - case System.Globalization.UnicodeCategory.InitialQuotePunctuation: - case System.Globalization.UnicodeCategory.FinalQuotePunctuation: - case System.Globalization.UnicodeCategory.OtherPunctuation: - case System.Globalization.UnicodeCategory.MathSymbol: - case System.Globalization.UnicodeCategory.ModifierSymbol: - case System.Globalization.UnicodeCategory.OtherSymbol: - case System.Globalization.UnicodeCategory.OtherNotAssigned: - return true; - } - - return false; - } + return this.Codepoint < 0x10000 && char.IsWhiteSpace((char)this.Codepoint); } } /// - /// Build a GameFontLayoutPlan. + /// Gets a value indicating whether whether this codepoint is a line break character. /// - public class Builder + public bool IsLineBreak { - private readonly ImFontPtr fontPtr; - private readonly FdtReader fdt; - private readonly string text; - private int maxWidth = int.MaxValue; - private float size; - private HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left; - - /// - /// Initializes a new instance of the class. - /// - /// Corresponding ImFontPtr. - /// FDT file to base on. - /// Text. - public Builder(ImFontPtr fontPtr, FdtReader fdt, string text) + get { - this.fontPtr = fontPtr; - this.fdt = fdt; - this.text = text; - this.size = fdt.FontHeader.LineHeight; + return this.Codepoint == '\n' || this.Codepoint == '\r'; } + } - /// - /// Sets the size of resulting text. - /// - /// Size in pixels. - /// This. - public Builder WithSize(float size) + /// + /// Gets a value indicating whether whether this codepoint is a chinese character. + /// + public bool IsChineseCharacter + { + get { - this.size = size; - return this; + // CJK Symbols and Punctuation(〇) + if (this.Codepoint >= 0x3007 && this.Codepoint <= 0x3007) + return true; + + // CJK Unified Ideographs Extension A + if (this.Codepoint >= 0x3400 && this.Codepoint <= 0x4DBF) + return true; + + // CJK Unified Ideographs + if (this.Codepoint >= 0x4E00 && this.Codepoint <= 0x9FFF) + return true; + + // CJK Unified Ideographs Extension B + if (this.Codepoint >= 0x20000 && this.Codepoint <= 0x2A6DF) + return true; + + // CJK Unified Ideographs Extension C + if (this.Codepoint >= 0x2A700 && this.Codepoint <= 0x2B73F) + return true; + + // CJK Unified Ideographs Extension D + if (this.Codepoint >= 0x2B740 && this.Codepoint <= 0x2B81F) + return true; + + // CJK Unified Ideographs Extension E + if (this.Codepoint >= 0x2B820 && this.Codepoint <= 0x2CEAF) + return true; + + // CJK Unified Ideographs Extension F + if (this.Codepoint >= 0x2CEB0 && this.Codepoint <= 0x2EBEF) + return true; + + return false; } + } - /// - /// Sets the maximum width of the text. - /// - /// Maximum width in pixels. - /// This. - public Builder WithMaxWidth(int maxWidth) + /// + /// Gets a value indicating whether whether this codepoint is a good position to break word after. + /// + public bool IsWordBreakPoint + { + get { - this.maxWidth = maxWidth; - return this; - } + if (this.IsChineseCharacter) + return true; - /// - /// Sets the horizontal alignment of the text. - /// - /// Horizontal alignment. - /// This. - public Builder WithHorizontalAlignment(HorizontalAlignment horizontalAlignment) - { - this.horizontalAlignment = horizontalAlignment; - return this; - } + if (this.Codepoint >= 0x10000) + return false; - /// - /// Builds the layout plan. - /// - /// Newly created layout plan. - public GameFontLayoutPlan Build() - { - var scale = this.size / this.fdt.FontHeader.LineHeight; - var unscaledMaxWidth = (float)Math.Ceiling(this.maxWidth / scale); - var elements = new List(); - foreach (var c in this.text) - elements.Add(new() { Codepoint = c, Glyph = this.fdt.GetGlyph(c), }); - - var lastBreakIndex = 0; - List lineBreakIndices = new() { 0 }; - for (var i = 1; i < elements.Count; i++) + // TODO: Whatever + switch (char.GetUnicodeCategory((char)this.Codepoint)) { - var prev = elements[i - 1]; - var curr = elements[i]; + case System.Globalization.UnicodeCategory.SpaceSeparator: + case System.Globalization.UnicodeCategory.LineSeparator: + case System.Globalization.UnicodeCategory.ParagraphSeparator: + case System.Globalization.UnicodeCategory.Control: + case System.Globalization.UnicodeCategory.Format: + case System.Globalization.UnicodeCategory.Surrogate: + case System.Globalization.UnicodeCategory.PrivateUse: + case System.Globalization.UnicodeCategory.ConnectorPunctuation: + case System.Globalization.UnicodeCategory.DashPunctuation: + case System.Globalization.UnicodeCategory.OpenPunctuation: + case System.Globalization.UnicodeCategory.ClosePunctuation: + case System.Globalization.UnicodeCategory.InitialQuotePunctuation: + case System.Globalization.UnicodeCategory.FinalQuotePunctuation: + case System.Globalization.UnicodeCategory.OtherPunctuation: + case System.Globalization.UnicodeCategory.MathSymbol: + case System.Globalization.UnicodeCategory.ModifierSymbol: + case System.Globalization.UnicodeCategory.OtherSymbol: + case System.Globalization.UnicodeCategory.OtherNotAssigned: + return true; + } - if (prev.IsLineBreak) - { - curr.X = 0; - curr.Y = prev.Y + this.fdt.FontHeader.LineHeight; - lineBreakIndices.Add(i); - } - else - { - curr.X = prev.X + prev.Glyph.NextOffsetX + prev.Glyph.BoundingWidth + this.fdt.GetDistance(prev.Codepoint, curr.Codepoint); - curr.Y = prev.Y; - } + return false; + } + } + } - if (prev.IsWordBreakPoint) - lastBreakIndex = i; + /// + /// Build a GameFontLayoutPlan. + /// + public class Builder + { + private readonly ImFontPtr fontPtr; + private readonly FdtReader fdt; + private readonly string text; + private int maxWidth = int.MaxValue; + private float size; + private HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left; - if (curr.IsSpace) - continue; + /// + /// Initializes a new instance of the class. + /// + /// Corresponding ImFontPtr. + /// FDT file to base on. + /// Text. + public Builder(ImFontPtr fontPtr, FdtReader fdt, string text) + { + this.fontPtr = fontPtr; + this.fdt = fdt; + this.text = text; + this.size = fdt.FontHeader.LineHeight; + } - if (curr.X + curr.Glyph.BoundingWidth < unscaledMaxWidth) - continue; + /// + /// Sets the size of resulting text. + /// + /// Size in pixels. + /// This. + public Builder WithSize(float size) + { + this.size = size; + return this; + } - if (!prev.IsSpace && elements[lastBreakIndex].X > 0) - { - prev = elements[lastBreakIndex - 1]; - curr = elements[lastBreakIndex]; - i = lastBreakIndex; - } - else - { - lastBreakIndex = i; - } + /// + /// Sets the maximum width of the text. + /// + /// Maximum width in pixels. + /// This. + public Builder WithMaxWidth(int maxWidth) + { + this.maxWidth = maxWidth; + return this; + } + /// + /// Sets the horizontal alignment of the text. + /// + /// Horizontal alignment. + /// This. + public Builder WithHorizontalAlignment(HorizontalAlignment horizontalAlignment) + { + this.horizontalAlignment = horizontalAlignment; + return this; + } + + /// + /// Builds the layout plan. + /// + /// Newly created layout plan. + public GameFontLayoutPlan Build() + { + var scale = this.size / this.fdt.FontHeader.LineHeight; + var unscaledMaxWidth = (float)Math.Ceiling(this.maxWidth / scale); + var elements = new List(); + foreach (var c in this.text) + elements.Add(new() { Codepoint = c, Glyph = this.fdt.GetGlyph(c), }); + + var lastBreakIndex = 0; + List lineBreakIndices = new() { 0 }; + for (var i = 1; i < elements.Count; i++) + { + var prev = elements[i - 1]; + var curr = elements[i]; + + if (prev.IsLineBreak) + { curr.X = 0; curr.Y = prev.Y + this.fdt.FontHeader.LineHeight; lineBreakIndices.Add(i); } - - lineBreakIndices.Add(elements.Count); - - var targetX = 0f; - var targetWidth = 0f; - var targetHeight = 0f; - for (var i = 1; i < lineBreakIndices.Count; i++) + else { - var from = lineBreakIndices[i - 1]; - var to = lineBreakIndices[i]; - while (to > from && elements[to - 1].IsSpace) - { - to--; - } - - if (from >= to) - continue; - - var right = 0f; - for (var j = from; j < to; j++) - { - var e = elements[j]; - right = Math.Max(right, e.X + Math.Max(e.Glyph.BoundingWidth, e.Glyph.AdvanceWidth)); - targetHeight = Math.Max(targetHeight, e.Y + e.Glyph.BoundingHeight); - } - - targetWidth = Math.Max(targetWidth, right - elements[from].X); - float offsetX; - if (this.horizontalAlignment == HorizontalAlignment.Center) - offsetX = (unscaledMaxWidth - right) / 2; - else if (this.horizontalAlignment == HorizontalAlignment.Right) - offsetX = unscaledMaxWidth - right; - else if (this.horizontalAlignment == HorizontalAlignment.Left) - offsetX = 0; - else - throw new ArgumentException("Invalid horizontal alignment"); - for (var j = from; j < to; j++) - elements[j].X += offsetX; - targetX = i == 1 ? elements[from].X : Math.Min(targetX, elements[from].X); + curr.X = prev.X + prev.Glyph.NextOffsetX + prev.Glyph.BoundingWidth + this.fdt.GetDistance(prev.Codepoint, curr.Codepoint); + curr.Y = prev.Y; } - targetHeight = Math.Max(targetHeight, this.fdt.FontHeader.LineHeight * (lineBreakIndices.Count - 1)); + if (prev.IsWordBreakPoint) + lastBreakIndex = i; - targetWidth *= scale; - targetHeight *= scale; - targetX *= scale; - foreach (var e in elements) + if (curr.IsSpace) + continue; + + if (curr.X + curr.Glyph.BoundingWidth < unscaledMaxWidth) + continue; + + if (!prev.IsSpace && elements[lastBreakIndex].X > 0) { - e.X *= scale; - e.Y *= scale; + prev = elements[lastBreakIndex - 1]; + curr = elements[lastBreakIndex]; + i = lastBreakIndex; + } + else + { + lastBreakIndex = i; } - return new GameFontLayoutPlan() - { - ImFontPtr = this.fontPtr, - Size = this.size, - X = targetX, - Width = targetWidth, - Height = targetHeight, - Elements = elements, - }; + curr.X = 0; + curr.Y = prev.Y + this.fdt.FontHeader.LineHeight; + lineBreakIndices.Add(i); } + + lineBreakIndices.Add(elements.Count); + + var targetX = 0f; + var targetWidth = 0f; + var targetHeight = 0f; + for (var i = 1; i < lineBreakIndices.Count; i++) + { + var from = lineBreakIndices[i - 1]; + var to = lineBreakIndices[i]; + while (to > from && elements[to - 1].IsSpace) + { + to--; + } + + if (from >= to) + continue; + + var right = 0f; + for (var j = from; j < to; j++) + { + var e = elements[j]; + right = Math.Max(right, e.X + Math.Max(e.Glyph.BoundingWidth, e.Glyph.AdvanceWidth)); + targetHeight = Math.Max(targetHeight, e.Y + e.Glyph.BoundingHeight); + } + + targetWidth = Math.Max(targetWidth, right - elements[from].X); + float offsetX; + if (this.horizontalAlignment == HorizontalAlignment.Center) + offsetX = (unscaledMaxWidth - right) / 2; + else if (this.horizontalAlignment == HorizontalAlignment.Right) + offsetX = unscaledMaxWidth - right; + else if (this.horizontalAlignment == HorizontalAlignment.Left) + offsetX = 0; + else + throw new ArgumentException("Invalid horizontal alignment"); + for (var j = from; j < to; j++) + elements[j].X += offsetX; + targetX = i == 1 ? elements[from].X : Math.Min(targetX, elements[from].X); + } + + targetHeight = Math.Max(targetHeight, this.fdt.FontHeader.LineHeight * (lineBreakIndices.Count - 1)); + + targetWidth *= scale; + targetHeight *= scale; + targetX *= scale; + foreach (var e in elements) + { + e.X *= scale; + e.Y *= scale; + } + + return new GameFontLayoutPlan() + { + ImFontPtr = this.fontPtr, + Size = this.size, + X = targetX, + Width = targetWidth, + Height = targetHeight, + Elements = elements, + }; } } } diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs index bd6e9bc99..ad0e47273 100644 --- a/Dalamud/Interface/GameFonts/GameFontManager.cs +++ b/Dalamud/Interface/GameFonts/GameFontManager.cs @@ -16,456 +16,455 @@ using Serilog; using static Dalamud.Interface.ImGuiHelpers; -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Loads game font for use in ImGui. +/// +[ServiceManager.EarlyLoadedService] +internal class GameFontManager : IServiceType { - /// - /// Loads game font for use in ImGui. - /// - [ServiceManager.EarlyLoadedService] - internal class GameFontManager : IServiceType + private static readonly string?[] FontNames = { - private static readonly string?[] FontNames = - { - null, - "AXIS_96", "AXIS_12", "AXIS_14", "AXIS_18", "AXIS_36", - "Jupiter_16", "Jupiter_20", "Jupiter_23", "Jupiter_45", "Jupiter_46", "Jupiter_90", - "Meidinger_16", "Meidinger_20", "Meidinger_40", - "MiedingerMid_10", "MiedingerMid_12", "MiedingerMid_14", "MiedingerMid_18", "MiedingerMid_36", - "TrumpGothic_184", "TrumpGothic_23", "TrumpGothic_34", "TrumpGothic_68", - }; + null, + "AXIS_96", "AXIS_12", "AXIS_14", "AXIS_18", "AXIS_36", + "Jupiter_16", "Jupiter_20", "Jupiter_23", "Jupiter_45", "Jupiter_46", "Jupiter_90", + "Meidinger_16", "Meidinger_20", "Meidinger_40", + "MiedingerMid_10", "MiedingerMid_12", "MiedingerMid_14", "MiedingerMid_18", "MiedingerMid_36", + "TrumpGothic_184", "TrumpGothic_23", "TrumpGothic_34", "TrumpGothic_68", + }; - private readonly object syncRoot = new(); + private readonly object syncRoot = new(); - private readonly FdtReader?[] fdts; - private readonly List texturePixels; - private readonly Dictionary fonts = new(); - private readonly Dictionary fontUseCounter = new(); - private readonly Dictionary>> glyphRectIds = new(); + private readonly FdtReader?[] fdts; + private readonly List texturePixels; + private readonly Dictionary fonts = new(); + private readonly Dictionary fontUseCounter = new(); + private readonly Dictionary>> glyphRectIds = new(); #pragma warning disable CS0414 - private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false; + private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false; #pragma warning restore CS0414 - [ServiceManager.ServiceConstructor] - private GameFontManager(DataManager dataManager) + [ServiceManager.ServiceConstructor] + private GameFontManager(DataManager dataManager) + { + using (Timings.Start("Getting fdt data")) { - using (Timings.Start("Getting fdt data")) - { - this.fdts = FontNames.Select(fontName => fontName == null ? null : new FdtReader(dataManager.GetFile($"common/font/{fontName}.fdt")!.Data)).ToArray(); - } - - using (Timings.Start("Getting texture data")) - { - var texTasks = Enumerable - .Range(1, 1 + this.fdts - .Where(x => x != null) - .Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max()) - .Max()) - .Select(x => dataManager.GetFile($"common/font/font{x}.tex")!) - .Select(x => new Task(Timings.AttachTimingHandle(() => x.ImageData!))) - .ToArray(); - foreach (var task in texTasks) - task.Start(); - this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList(); - } + this.fdts = FontNames.Select(fontName => fontName == null ? null : new FdtReader(dataManager.GetFile($"common/font/{fontName}.fdt")!.Data)).ToArray(); } - /// - /// Describe font into a string. - /// - /// Font to describe. - /// A string in a form of "FontName (NNNpt)". - public static string DescribeFont(GameFontFamilyAndSize font) + using (Timings.Start("Getting texture data")) { - return font switch - { - GameFontFamilyAndSize.Undefined => "-", - GameFontFamilyAndSize.Axis96 => "AXIS (9.6pt)", - GameFontFamilyAndSize.Axis12 => "AXIS (12pt)", - GameFontFamilyAndSize.Axis14 => "AXIS (14pt)", - GameFontFamilyAndSize.Axis18 => "AXIS (18pt)", - GameFontFamilyAndSize.Axis36 => "AXIS (36pt)", - GameFontFamilyAndSize.Jupiter16 => "Jupiter (16pt)", - GameFontFamilyAndSize.Jupiter20 => "Jupiter (20pt)", - GameFontFamilyAndSize.Jupiter23 => "Jupiter (23pt)", - GameFontFamilyAndSize.Jupiter45 => "Jupiter Numeric (45pt)", - GameFontFamilyAndSize.Jupiter46 => "Jupiter (46pt)", - GameFontFamilyAndSize.Jupiter90 => "Jupiter Numeric (90pt)", - GameFontFamilyAndSize.Meidinger16 => "Meidinger Numeric (16pt)", - GameFontFamilyAndSize.Meidinger20 => "Meidinger Numeric (20pt)", - GameFontFamilyAndSize.Meidinger40 => "Meidinger Numeric (40pt)", - GameFontFamilyAndSize.MiedingerMid10 => "MiedingerMid (10pt)", - GameFontFamilyAndSize.MiedingerMid12 => "MiedingerMid (12pt)", - GameFontFamilyAndSize.MiedingerMid14 => "MiedingerMid (14pt)", - GameFontFamilyAndSize.MiedingerMid18 => "MiedingerMid (18pt)", - GameFontFamilyAndSize.MiedingerMid36 => "MiedingerMid (36pt)", - GameFontFamilyAndSize.TrumpGothic184 => "Trump Gothic (18.4pt)", - GameFontFamilyAndSize.TrumpGothic23 => "Trump Gothic (23pt)", - GameFontFamilyAndSize.TrumpGothic34 => "Trump Gothic (34pt)", - GameFontFamilyAndSize.TrumpGothic68 => "Trump Gothic (68pt)", - _ => throw new ArgumentOutOfRangeException(nameof(font), font, "Invalid argument"), - }; - } - - /// - /// Determines whether a font should be able to display most of stuff. - /// - /// Font to check. - /// True if it can. - public static bool IsGenericPurposeFont(GameFontFamilyAndSize font) - { - return font switch - { - GameFontFamilyAndSize.Axis96 => true, - GameFontFamilyAndSize.Axis12 => true, - GameFontFamilyAndSize.Axis14 => true, - GameFontFamilyAndSize.Axis18 => true, - GameFontFamilyAndSize.Axis36 => true, - _ => false, - }; - } - - /// - /// Unscales fonts after they have been rendered onto atlas. - /// - /// Font to unscale. - /// Scale factor. - /// Whether to call target.BuildLookupTable(). - public static void UnscaleFont(ImFontPtr fontPtr, float fontScale, bool rebuildLookupTable = true) - { - if (fontScale == 1) - return; - - unsafe - { - var font = fontPtr.NativePtr; - for (int i = 0, i_ = font->IndexedHotData.Size; i < i_; ++i) - { - font->IndexedHotData.Ref(i).AdvanceX /= fontScale; - font->IndexedHotData.Ref(i).OccupiedWidth /= fontScale; - } - - font->FontSize /= fontScale; - font->Ascent /= fontScale; - font->Descent /= fontScale; - if (font->ConfigData != null) - font->ConfigData->SizePixels /= fontScale; - var glyphs = (ImFontGlyphReal*)font->Glyphs.Data; - for (int i = 0, i_ = font->Glyphs.Size; i < i_; i++) - { - var glyph = &glyphs[i]; - glyph->X0 /= fontScale; - glyph->X1 /= fontScale; - glyph->Y0 /= fontScale; - glyph->Y1 /= fontScale; - glyph->AdvanceX /= fontScale; - } - - for (int i = 0, i_ = font->KerningPairs.Size; i < i_; i++) - font->KerningPairs.Ref(i).AdvanceXAdjustment /= fontScale; - for (int i = 0, i_ = font->FrequentKerningPairs.Size; i < i_; i++) - font->FrequentKerningPairs.Ref(i) /= fontScale; - } - - if (rebuildLookupTable && fontPtr.Glyphs.Size > 0) - fontPtr.BuildLookupTable(); - } - - /// - /// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process. - /// - /// Font to use. - /// Handle to game font that may or may not be ready yet. - public GameFontHandle NewFontRef(GameFontStyle style) - { - var interfaceManager = Service.Get(); - var needRebuild = false; - - lock (this.syncRoot) - { - this.fontUseCounter[style] = this.fontUseCounter.GetValueOrDefault(style, 0) + 1; - } - - needRebuild = !this.fonts.ContainsKey(style); - if (needRebuild) - { - Log.Information("[GameFontManager] NewFontRef: Queueing RebuildFonts because {0} has been requested.", style.ToString()); - Service.GetAsync() - .ContinueWith(task => task.Result.RunOnTick(() => interfaceManager.RebuildFonts())); - } - - return new(this, style); - } - - /// - /// Gets the font. - /// - /// Font to get. - /// Corresponding font or null. - public ImFontPtr? GetFont(GameFontStyle style) => this.fonts.GetValueOrDefault(style, null); - - /// - /// Gets the corresponding FdtReader. - /// - /// Font to get. - /// Corresponding FdtReader or null. - public FdtReader? GetFdtReader(GameFontFamilyAndSize family) => this.fdts[(int)family]; - - /// - /// Fills missing glyphs in target font from source font, if both are not null. - /// - /// Source font. - /// Target font. - /// Whether to copy missing glyphs only. - /// Whether to call target.BuildLookupTable(). - public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable) - { - ImGuiHelpers.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable); - } - - /// - /// Fills missing glyphs in target font from source font, if both are not null. - /// - /// Source font. - /// Target font. - /// Whether to copy missing glyphs only. - /// Whether to call target.BuildLookupTable(). - public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable) - { - ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable); - } - - /// - /// Fills missing glyphs in target font from source font, if both are not null. - /// - /// Source font. - /// Target font. - /// Whether to copy missing glyphs only. - /// Whether to call target.BuildLookupTable(). - public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable) - { - ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable); - } - - /// - /// Build fonts before plugins do something more. To be called from InterfaceManager. - /// - public void BuildFonts() - { - this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = true; - - this.glyphRectIds.Clear(); - this.fonts.Clear(); - - lock (this.syncRoot) - { - foreach (var style in this.fontUseCounter.Keys) - this.EnsureFont(style); - } - } - - /// - /// Record that ImGui.GetIO().Fonts.Build() has been called. - /// - public void AfterIoFontsBuild() - { - this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false; - } - - /// - /// Checks whether GameFontMamager owns an ImFont. - /// - /// ImFontPtr to check. - /// Whether it owns. - public bool OwnsFont(ImFontPtr fontPtr) => this.fonts.ContainsValue(fontPtr); - - /// - /// Post-build fonts before plugins do something more. To be called from InterfaceManager. - /// - public unsafe void AfterBuildFonts() - { - var interfaceManager = Service.Get(); - var ioFonts = ImGui.GetIO().Fonts; - var fontGamma = interfaceManager.FontGamma; - - var pixels8s = new byte*[ioFonts.Textures.Size]; - var pixels32s = new uint*[ioFonts.Textures.Size]; - var widths = new int[ioFonts.Textures.Size]; - var heights = new int[ioFonts.Textures.Size]; - for (var i = 0; i < pixels8s.Length; i++) - { - ioFonts.GetTexDataAsRGBA32(i, out pixels8s[i], out widths[i], out heights[i]); - pixels32s[i] = (uint*)pixels8s[i]; - } - - foreach (var (style, font) in this.fonts) - { - var fdt = this.fdts[(int)style.FamilyAndSize]; - var scale = style.SizePt / fdt.FontHeader.Size; - var fontPtr = font.NativePtr; - - Log.Verbose("[GameFontManager] AfterBuildFonts: Scaling {0} from {1}pt to {2}pt (scale: {3})", style.ToString(), fdt.FontHeader.Size, style.SizePt, scale); - - fontPtr->FontSize = fdt.FontHeader.Size * 4 / 3; - if (fontPtr->ConfigData != null) - fontPtr->ConfigData->SizePixels = fontPtr->FontSize; - fontPtr->Ascent = fdt.FontHeader.Ascent; - fontPtr->Descent = fdt.FontHeader.Descent; - fontPtr->EllipsisChar = '…'; - foreach (var fallbackCharCandidate in "〓?!") - { - var glyph = font.FindGlyphNoFallback(fallbackCharCandidate); - if ((IntPtr)glyph.NativePtr != IntPtr.Zero) - { - var ptr = font.NativePtr; - ptr->FallbackChar = fallbackCharCandidate; - ptr->FallbackGlyph = glyph.NativePtr; - ptr->FallbackHotData = (ImFontGlyphHotData*)ptr->IndexedHotData.Address(fallbackCharCandidate); - break; - } - } - - // I have no idea what's causing NPE, so just to be safe - try - { - if (font.NativePtr != null && font.NativePtr->ConfigData != null) - { - var nameBytes = Encoding.UTF8.GetBytes(style.ToString() + "\0"); - Marshal.Copy(nameBytes, 0, (IntPtr)font.ConfigData.Name.Data, Math.Min(nameBytes.Length, font.ConfigData.Name.Count)); - } - } - catch (NullReferenceException) - { - // do nothing - } - - foreach (var (c, (rectId, glyph)) in this.glyphRectIds[style]) - { - var rc = (ImFontAtlasCustomRectReal*)ioFonts.GetCustomRectByIndex(rectId).NativePtr; - var pixels8 = pixels8s[rc->TextureIndex]; - var pixels32 = pixels32s[rc->TextureIndex]; - var width = widths[rc->TextureIndex]; - var height = heights[rc->TextureIndex]; - var sourceBuffer = this.texturePixels[glyph.TextureFileIndex]; - var sourceBufferDelta = glyph.TextureChannelByteIndex; - var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph); - if (widthAdjustment == 0) - { - for (var y = 0; y < glyph.BoundingHeight; y++) - { - for (var x = 0; x < glyph.BoundingWidth; x++) - { - var a = sourceBuffer[sourceBufferDelta + (4 * (((glyph.TextureOffsetY + y) * fdt.FontHeader.TextureWidth) + glyph.TextureOffsetX + x))]; - pixels32[((rc->Y + y) * width) + rc->X + x] = (uint)(a << 24) | 0xFFFFFFu; - } - } - } - else - { - for (var y = 0; y < glyph.BoundingHeight; y++) - { - for (var x = 0; x < glyph.BoundingWidth + widthAdjustment; x++) - pixels32[((rc->Y + y) * width) + rc->X + x] = 0xFFFFFFu; - } - - for (int xbold = 0, xbold_ = Math.Max(1, (int)Math.Ceiling(style.Weight + 1)); xbold < xbold_; xbold++) - { - var boldStrength = Math.Min(1f, style.Weight + 1 - xbold); - for (var y = 0; y < glyph.BoundingHeight; y++) - { - float xDelta = xbold; - if (style.BaseSkewStrength > 0) - xDelta += style.BaseSkewStrength * (fdt.FontHeader.LineHeight - glyph.CurrentOffsetY - y) / fdt.FontHeader.LineHeight; - else if (style.BaseSkewStrength < 0) - xDelta -= style.BaseSkewStrength * (glyph.CurrentOffsetY + y) / fdt.FontHeader.LineHeight; - var xDeltaInt = (int)Math.Floor(xDelta); - var xness = xDelta - xDeltaInt; - for (var x = 0; x < glyph.BoundingWidth; x++) - { - var sourcePixelIndex = ((glyph.TextureOffsetY + y) * fdt.FontHeader.TextureWidth) + glyph.TextureOffsetX + x; - var a1 = sourceBuffer[sourceBufferDelta + (4 * sourcePixelIndex)]; - var a2 = x == glyph.BoundingWidth - 1 ? 0 : sourceBuffer[sourceBufferDelta + (4 * (sourcePixelIndex + 1))]; - var n = (a1 * xness) + (a2 * (1 - xness)); - var targetOffset = ((rc->Y + y) * width) + rc->X + x + xDeltaInt; - pixels8[(targetOffset * 4) + 3] = Math.Max(pixels8[(targetOffset * 4) + 3], (byte)(boldStrength * n)); - } - } - } - } - - if (Math.Abs(fontGamma - 1.4f) >= 0.001) - { - // Gamma correction (stbtt/FreeType would output in linear space whereas most real world usages will apply 1.4 or 1.8 gamma; Windows/XIV prebaked uses 1.4) - for (int y = rc->Y, y_ = rc->Y + rc->Height; y < y_; y++) - { - for (int x = rc->X, x_ = rc->X + rc->Width; x < x_; x++) - { - var i = (((y * width) + x) * 4) + 3; - pixels8[i] = (byte)(Math.Pow(pixels8[i] / 255.0f, 1.4f / fontGamma) * 255.0f); - } - } - } - } - - UnscaleFont(font, 1 / scale, false); - } - } - - /// - /// Decrease font reference counter. - /// - /// Font to release. - internal void DecreaseFontRef(GameFontStyle style) - { - lock (this.syncRoot) - { - if (!this.fontUseCounter.ContainsKey(style)) - return; - - if ((this.fontUseCounter[style] -= 1) == 0) - this.fontUseCounter.Remove(style); - } - } - - private unsafe void EnsureFont(GameFontStyle style) - { - var rectIds = this.glyphRectIds[style] = new(); - - var fdt = this.fdts[(int)style.FamilyAndSize]; - if (fdt == null) - return; - - ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); - fontConfig.OversampleH = 1; - fontConfig.OversampleV = 1; - fontConfig.PixelSnapH = false; - - var io = ImGui.GetIO(); - var font = io.Fonts.AddFontDefault(fontConfig); - - fontConfig.Destroy(); - - this.fonts[style] = font; - foreach (var glyph in fdt.Glyphs) - { - var c = glyph.Char; - if (c < 32 || c >= 0xFFFF) - continue; - - var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph); - rectIds[c] = Tuple.Create( - io.Fonts.AddCustomRectFontGlyph( - font, - c, - glyph.BoundingWidth + widthAdjustment, - glyph.BoundingHeight, - glyph.AdvanceWidth, - new Vector2(0, glyph.CurrentOffsetY)), - glyph); - } - - foreach (var kernPair in fdt.Distances) - font.AddKerningPair(kernPair.Left, kernPair.Right, kernPair.RightOffset); + var texTasks = Enumerable + .Range(1, 1 + this.fdts + .Where(x => x != null) + .Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max()) + .Max()) + .Select(x => dataManager.GetFile($"common/font/font{x}.tex")!) + .Select(x => new Task(Timings.AttachTimingHandle(() => x.ImageData!))) + .ToArray(); + foreach (var task in texTasks) + task.Start(); + this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList(); } } + + /// + /// Describe font into a string. + /// + /// Font to describe. + /// A string in a form of "FontName (NNNpt)". + public static string DescribeFont(GameFontFamilyAndSize font) + { + return font switch + { + GameFontFamilyAndSize.Undefined => "-", + GameFontFamilyAndSize.Axis96 => "AXIS (9.6pt)", + GameFontFamilyAndSize.Axis12 => "AXIS (12pt)", + GameFontFamilyAndSize.Axis14 => "AXIS (14pt)", + GameFontFamilyAndSize.Axis18 => "AXIS (18pt)", + GameFontFamilyAndSize.Axis36 => "AXIS (36pt)", + GameFontFamilyAndSize.Jupiter16 => "Jupiter (16pt)", + GameFontFamilyAndSize.Jupiter20 => "Jupiter (20pt)", + GameFontFamilyAndSize.Jupiter23 => "Jupiter (23pt)", + GameFontFamilyAndSize.Jupiter45 => "Jupiter Numeric (45pt)", + GameFontFamilyAndSize.Jupiter46 => "Jupiter (46pt)", + GameFontFamilyAndSize.Jupiter90 => "Jupiter Numeric (90pt)", + GameFontFamilyAndSize.Meidinger16 => "Meidinger Numeric (16pt)", + GameFontFamilyAndSize.Meidinger20 => "Meidinger Numeric (20pt)", + GameFontFamilyAndSize.Meidinger40 => "Meidinger Numeric (40pt)", + GameFontFamilyAndSize.MiedingerMid10 => "MiedingerMid (10pt)", + GameFontFamilyAndSize.MiedingerMid12 => "MiedingerMid (12pt)", + GameFontFamilyAndSize.MiedingerMid14 => "MiedingerMid (14pt)", + GameFontFamilyAndSize.MiedingerMid18 => "MiedingerMid (18pt)", + GameFontFamilyAndSize.MiedingerMid36 => "MiedingerMid (36pt)", + GameFontFamilyAndSize.TrumpGothic184 => "Trump Gothic (18.4pt)", + GameFontFamilyAndSize.TrumpGothic23 => "Trump Gothic (23pt)", + GameFontFamilyAndSize.TrumpGothic34 => "Trump Gothic (34pt)", + GameFontFamilyAndSize.TrumpGothic68 => "Trump Gothic (68pt)", + _ => throw new ArgumentOutOfRangeException(nameof(font), font, "Invalid argument"), + }; + } + + /// + /// Determines whether a font should be able to display most of stuff. + /// + /// Font to check. + /// True if it can. + public static bool IsGenericPurposeFont(GameFontFamilyAndSize font) + { + return font switch + { + GameFontFamilyAndSize.Axis96 => true, + GameFontFamilyAndSize.Axis12 => true, + GameFontFamilyAndSize.Axis14 => true, + GameFontFamilyAndSize.Axis18 => true, + GameFontFamilyAndSize.Axis36 => true, + _ => false, + }; + } + + /// + /// Unscales fonts after they have been rendered onto atlas. + /// + /// Font to unscale. + /// Scale factor. + /// Whether to call target.BuildLookupTable(). + public static void UnscaleFont(ImFontPtr fontPtr, float fontScale, bool rebuildLookupTable = true) + { + if (fontScale == 1) + return; + + unsafe + { + var font = fontPtr.NativePtr; + for (int i = 0, i_ = font->IndexedHotData.Size; i < i_; ++i) + { + font->IndexedHotData.Ref(i).AdvanceX /= fontScale; + font->IndexedHotData.Ref(i).OccupiedWidth /= fontScale; + } + + font->FontSize /= fontScale; + font->Ascent /= fontScale; + font->Descent /= fontScale; + if (font->ConfigData != null) + font->ConfigData->SizePixels /= fontScale; + var glyphs = (ImFontGlyphReal*)font->Glyphs.Data; + for (int i = 0, i_ = font->Glyphs.Size; i < i_; i++) + { + var glyph = &glyphs[i]; + glyph->X0 /= fontScale; + glyph->X1 /= fontScale; + glyph->Y0 /= fontScale; + glyph->Y1 /= fontScale; + glyph->AdvanceX /= fontScale; + } + + for (int i = 0, i_ = font->KerningPairs.Size; i < i_; i++) + font->KerningPairs.Ref(i).AdvanceXAdjustment /= fontScale; + for (int i = 0, i_ = font->FrequentKerningPairs.Size; i < i_; i++) + font->FrequentKerningPairs.Ref(i) /= fontScale; + } + + if (rebuildLookupTable && fontPtr.Glyphs.Size > 0) + fontPtr.BuildLookupTable(); + } + + /// + /// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process. + /// + /// Font to use. + /// Handle to game font that may or may not be ready yet. + public GameFontHandle NewFontRef(GameFontStyle style) + { + var interfaceManager = Service.Get(); + var needRebuild = false; + + lock (this.syncRoot) + { + this.fontUseCounter[style] = this.fontUseCounter.GetValueOrDefault(style, 0) + 1; + } + + needRebuild = !this.fonts.ContainsKey(style); + if (needRebuild) + { + Log.Information("[GameFontManager] NewFontRef: Queueing RebuildFonts because {0} has been requested.", style.ToString()); + Service.GetAsync() + .ContinueWith(task => task.Result.RunOnTick(() => interfaceManager.RebuildFonts())); + } + + return new(this, style); + } + + /// + /// Gets the font. + /// + /// Font to get. + /// Corresponding font or null. + public ImFontPtr? GetFont(GameFontStyle style) => this.fonts.GetValueOrDefault(style, null); + + /// + /// Gets the corresponding FdtReader. + /// + /// Font to get. + /// Corresponding FdtReader or null. + public FdtReader? GetFdtReader(GameFontFamilyAndSize family) => this.fdts[(int)family]; + + /// + /// Fills missing glyphs in target font from source font, if both are not null. + /// + /// Source font. + /// Target font. + /// Whether to copy missing glyphs only. + /// Whether to call target.BuildLookupTable(). + public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable) + { + ImGuiHelpers.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable); + } + + /// + /// Fills missing glyphs in target font from source font, if both are not null. + /// + /// Source font. + /// Target font. + /// Whether to copy missing glyphs only. + /// Whether to call target.BuildLookupTable(). + public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable) + { + ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable); + } + + /// + /// Fills missing glyphs in target font from source font, if both are not null. + /// + /// Source font. + /// Target font. + /// Whether to copy missing glyphs only. + /// Whether to call target.BuildLookupTable(). + public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable) + { + ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable); + } + + /// + /// Build fonts before plugins do something more. To be called from InterfaceManager. + /// + public void BuildFonts() + { + this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = true; + + this.glyphRectIds.Clear(); + this.fonts.Clear(); + + lock (this.syncRoot) + { + foreach (var style in this.fontUseCounter.Keys) + this.EnsureFont(style); + } + } + + /// + /// Record that ImGui.GetIO().Fonts.Build() has been called. + /// + public void AfterIoFontsBuild() + { + this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false; + } + + /// + /// Checks whether GameFontMamager owns an ImFont. + /// + /// ImFontPtr to check. + /// Whether it owns. + public bool OwnsFont(ImFontPtr fontPtr) => this.fonts.ContainsValue(fontPtr); + + /// + /// Post-build fonts before plugins do something more. To be called from InterfaceManager. + /// + public unsafe void AfterBuildFonts() + { + var interfaceManager = Service.Get(); + var ioFonts = ImGui.GetIO().Fonts; + var fontGamma = interfaceManager.FontGamma; + + var pixels8s = new byte*[ioFonts.Textures.Size]; + var pixels32s = new uint*[ioFonts.Textures.Size]; + var widths = new int[ioFonts.Textures.Size]; + var heights = new int[ioFonts.Textures.Size]; + for (var i = 0; i < pixels8s.Length; i++) + { + ioFonts.GetTexDataAsRGBA32(i, out pixels8s[i], out widths[i], out heights[i]); + pixels32s[i] = (uint*)pixels8s[i]; + } + + foreach (var (style, font) in this.fonts) + { + var fdt = this.fdts[(int)style.FamilyAndSize]; + var scale = style.SizePt / fdt.FontHeader.Size; + var fontPtr = font.NativePtr; + + Log.Verbose("[GameFontManager] AfterBuildFonts: Scaling {0} from {1}pt to {2}pt (scale: {3})", style.ToString(), fdt.FontHeader.Size, style.SizePt, scale); + + fontPtr->FontSize = fdt.FontHeader.Size * 4 / 3; + if (fontPtr->ConfigData != null) + fontPtr->ConfigData->SizePixels = fontPtr->FontSize; + fontPtr->Ascent = fdt.FontHeader.Ascent; + fontPtr->Descent = fdt.FontHeader.Descent; + fontPtr->EllipsisChar = '…'; + foreach (var fallbackCharCandidate in "〓?!") + { + var glyph = font.FindGlyphNoFallback(fallbackCharCandidate); + if ((IntPtr)glyph.NativePtr != IntPtr.Zero) + { + var ptr = font.NativePtr; + ptr->FallbackChar = fallbackCharCandidate; + ptr->FallbackGlyph = glyph.NativePtr; + ptr->FallbackHotData = (ImFontGlyphHotData*)ptr->IndexedHotData.Address(fallbackCharCandidate); + break; + } + } + + // I have no idea what's causing NPE, so just to be safe + try + { + if (font.NativePtr != null && font.NativePtr->ConfigData != null) + { + var nameBytes = Encoding.UTF8.GetBytes(style.ToString() + "\0"); + Marshal.Copy(nameBytes, 0, (IntPtr)font.ConfigData.Name.Data, Math.Min(nameBytes.Length, font.ConfigData.Name.Count)); + } + } + catch (NullReferenceException) + { + // do nothing + } + + foreach (var (c, (rectId, glyph)) in this.glyphRectIds[style]) + { + var rc = (ImFontAtlasCustomRectReal*)ioFonts.GetCustomRectByIndex(rectId).NativePtr; + var pixels8 = pixels8s[rc->TextureIndex]; + var pixels32 = pixels32s[rc->TextureIndex]; + var width = widths[rc->TextureIndex]; + var height = heights[rc->TextureIndex]; + var sourceBuffer = this.texturePixels[glyph.TextureFileIndex]; + var sourceBufferDelta = glyph.TextureChannelByteIndex; + var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph); + if (widthAdjustment == 0) + { + for (var y = 0; y < glyph.BoundingHeight; y++) + { + for (var x = 0; x < glyph.BoundingWidth; x++) + { + var a = sourceBuffer[sourceBufferDelta + (4 * (((glyph.TextureOffsetY + y) * fdt.FontHeader.TextureWidth) + glyph.TextureOffsetX + x))]; + pixels32[((rc->Y + y) * width) + rc->X + x] = (uint)(a << 24) | 0xFFFFFFu; + } + } + } + else + { + for (var y = 0; y < glyph.BoundingHeight; y++) + { + for (var x = 0; x < glyph.BoundingWidth + widthAdjustment; x++) + pixels32[((rc->Y + y) * width) + rc->X + x] = 0xFFFFFFu; + } + + for (int xbold = 0, xbold_ = Math.Max(1, (int)Math.Ceiling(style.Weight + 1)); xbold < xbold_; xbold++) + { + var boldStrength = Math.Min(1f, style.Weight + 1 - xbold); + for (var y = 0; y < glyph.BoundingHeight; y++) + { + float xDelta = xbold; + if (style.BaseSkewStrength > 0) + xDelta += style.BaseSkewStrength * (fdt.FontHeader.LineHeight - glyph.CurrentOffsetY - y) / fdt.FontHeader.LineHeight; + else if (style.BaseSkewStrength < 0) + xDelta -= style.BaseSkewStrength * (glyph.CurrentOffsetY + y) / fdt.FontHeader.LineHeight; + var xDeltaInt = (int)Math.Floor(xDelta); + var xness = xDelta - xDeltaInt; + for (var x = 0; x < glyph.BoundingWidth; x++) + { + var sourcePixelIndex = ((glyph.TextureOffsetY + y) * fdt.FontHeader.TextureWidth) + glyph.TextureOffsetX + x; + var a1 = sourceBuffer[sourceBufferDelta + (4 * sourcePixelIndex)]; + var a2 = x == glyph.BoundingWidth - 1 ? 0 : sourceBuffer[sourceBufferDelta + (4 * (sourcePixelIndex + 1))]; + var n = (a1 * xness) + (a2 * (1 - xness)); + var targetOffset = ((rc->Y + y) * width) + rc->X + x + xDeltaInt; + pixels8[(targetOffset * 4) + 3] = Math.Max(pixels8[(targetOffset * 4) + 3], (byte)(boldStrength * n)); + } + } + } + } + + if (Math.Abs(fontGamma - 1.4f) >= 0.001) + { + // Gamma correction (stbtt/FreeType would output in linear space whereas most real world usages will apply 1.4 or 1.8 gamma; Windows/XIV prebaked uses 1.4) + for (int y = rc->Y, y_ = rc->Y + rc->Height; y < y_; y++) + { + for (int x = rc->X, x_ = rc->X + rc->Width; x < x_; x++) + { + var i = (((y * width) + x) * 4) + 3; + pixels8[i] = (byte)(Math.Pow(pixels8[i] / 255.0f, 1.4f / fontGamma) * 255.0f); + } + } + } + } + + UnscaleFont(font, 1 / scale, false); + } + } + + /// + /// Decrease font reference counter. + /// + /// Font to release. + internal void DecreaseFontRef(GameFontStyle style) + { + lock (this.syncRoot) + { + if (!this.fontUseCounter.ContainsKey(style)) + return; + + if ((this.fontUseCounter[style] -= 1) == 0) + this.fontUseCounter.Remove(style); + } + } + + private unsafe void EnsureFont(GameFontStyle style) + { + var rectIds = this.glyphRectIds[style] = new(); + + var fdt = this.fdts[(int)style.FamilyAndSize]; + if (fdt == null) + return; + + ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); + fontConfig.OversampleH = 1; + fontConfig.OversampleV = 1; + fontConfig.PixelSnapH = false; + + var io = ImGui.GetIO(); + var font = io.Fonts.AddFontDefault(fontConfig); + + fontConfig.Destroy(); + + this.fonts[style] = font; + foreach (var glyph in fdt.Glyphs) + { + var c = glyph.Char; + if (c < 32 || c >= 0xFFFF) + continue; + + var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph); + rectIds[c] = Tuple.Create( + io.Fonts.AddCustomRectFontGlyph( + font, + c, + glyph.BoundingWidth + widthAdjustment, + glyph.BoundingHeight, + glyph.AdvanceWidth, + new Vector2(0, glyph.CurrentOffsetY)), + glyph); + } + + foreach (var kernPair in fdt.Distances) + font.AddKerningPair(kernPair.Left, kernPair.Right, kernPair.RightOffset); + } } diff --git a/Dalamud/Interface/GameFonts/GameFontStyle.cs b/Dalamud/Interface/GameFonts/GameFontStyle.cs index 6ec078d69..40b810161 100644 --- a/Dalamud/Interface/GameFonts/GameFontStyle.cs +++ b/Dalamud/Interface/GameFonts/GameFontStyle.cs @@ -1,285 +1,284 @@ using System; -namespace Dalamud.Interface.GameFonts +namespace Dalamud.Interface.GameFonts; + +/// +/// Describes a font based on game resource file. +/// +public struct GameFontStyle { /// - /// Describes a font based on game resource file. + /// Font family of the font. /// - public struct GameFontStyle + public GameFontFamilyAndSize FamilyAndSize; + + /// + /// Size of the font in pixels unit. + /// + public float SizePx; + + /// + /// Weight of the font. + /// + /// 0 is unaltered. + /// Any value greater than 0 will make it bolder. + /// + public float Weight; + + /// + /// Skewedness of the font. + /// + /// 0 is unaltered. + /// Greater than 1 will make upper part go rightwards. + /// Less than 1 will make lower part go rightwards. + /// + public float SkewStrength; + + /// + /// Initializes a new instance of the struct. + /// + /// Font family. + /// Size in pixels. + public GameFontStyle(GameFontFamily family, float sizePx) { - /// - /// Font family of the font. - /// - public GameFontFamilyAndSize FamilyAndSize; + this.FamilyAndSize = GetRecommendedFamilyAndSize(family, sizePx * 3 / 4); + this.Weight = this.SkewStrength = 0f; + this.SizePx = sizePx; + } - /// - /// Size of the font in pixels unit. - /// - public float SizePx; + /// + /// Initializes a new instance of the struct. + /// + /// Font family and size. + public GameFontStyle(GameFontFamilyAndSize familyAndSize) + { + this.FamilyAndSize = familyAndSize; + this.Weight = this.SkewStrength = 0f; - /// - /// Weight of the font. - /// - /// 0 is unaltered. - /// Any value greater than 0 will make it bolder. - /// - public float Weight; + // Dummy assignment to satisfy requirements + this.SizePx = 0; - /// - /// Skewedness of the font. - /// - /// 0 is unaltered. - /// Greater than 1 will make upper part go rightwards. - /// Less than 1 will make lower part go rightwards. - /// - public float SkewStrength; + this.SizePx = this.BaseSizePx; + } - /// - /// Initializes a new instance of the struct. - /// - /// Font family. - /// Size in pixels. - public GameFontStyle(GameFontFamily family, float sizePx) + /// + /// Gets or sets the size of the font in points unit. + /// + public float SizePt + { + get => this.SizePx * 3 / 4; + set => this.SizePx = value * 4 / 3; + } + + /// + /// Gets or sets the base skew strength. + /// + public float BaseSkewStrength + { + get => this.SkewStrength * this.BaseSizePx / this.SizePx; + set => this.SkewStrength = value * this.SizePx / this.BaseSizePx; + } + + /// + /// Gets the font family. + /// + public GameFontFamily Family => this.FamilyAndSize switch + { + GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined, + GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis, + GameFontFamilyAndSize.Axis12 => GameFontFamily.Axis, + GameFontFamilyAndSize.Axis14 => GameFontFamily.Axis, + GameFontFamilyAndSize.Axis18 => GameFontFamily.Axis, + GameFontFamilyAndSize.Axis36 => GameFontFamily.Axis, + GameFontFamilyAndSize.Jupiter16 => GameFontFamily.Jupiter, + GameFontFamilyAndSize.Jupiter20 => GameFontFamily.Jupiter, + GameFontFamilyAndSize.Jupiter23 => GameFontFamily.Jupiter, + GameFontFamilyAndSize.Jupiter45 => GameFontFamily.JupiterNumeric, + GameFontFamilyAndSize.Jupiter46 => GameFontFamily.Jupiter, + GameFontFamilyAndSize.Jupiter90 => GameFontFamily.JupiterNumeric, + GameFontFamilyAndSize.Meidinger16 => GameFontFamily.Meidinger, + GameFontFamilyAndSize.Meidinger20 => GameFontFamily.Meidinger, + GameFontFamilyAndSize.Meidinger40 => GameFontFamily.Meidinger, + GameFontFamilyAndSize.MiedingerMid10 => GameFontFamily.MiedingerMid, + GameFontFamilyAndSize.MiedingerMid12 => GameFontFamily.MiedingerMid, + GameFontFamilyAndSize.MiedingerMid14 => GameFontFamily.MiedingerMid, + GameFontFamilyAndSize.MiedingerMid18 => GameFontFamily.MiedingerMid, + GameFontFamilyAndSize.MiedingerMid36 => GameFontFamily.MiedingerMid, + GameFontFamilyAndSize.TrumpGothic184 => GameFontFamily.TrumpGothic, + GameFontFamilyAndSize.TrumpGothic23 => GameFontFamily.TrumpGothic, + GameFontFamilyAndSize.TrumpGothic34 => GameFontFamily.TrumpGothic, + GameFontFamilyAndSize.TrumpGothic68 => GameFontFamily.TrumpGothic, + _ => throw new InvalidOperationException(), + }; + + /// + /// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes. + /// + public GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch + { + GameFontFamily.Axis => GameFontFamilyAndSize.Axis96, + GameFontFamily.Jupiter => GameFontFamilyAndSize.Jupiter16, + GameFontFamily.JupiterNumeric => GameFontFamilyAndSize.Jupiter45, + GameFontFamily.Meidinger => GameFontFamilyAndSize.Meidinger16, + GameFontFamily.MiedingerMid => GameFontFamilyAndSize.MiedingerMid10, + GameFontFamily.TrumpGothic => GameFontFamilyAndSize.TrumpGothic184, + _ => GameFontFamilyAndSize.Undefined, + }; + + /// + /// Gets the base font size in point unit. + /// + public float BaseSizePt => this.FamilyAndSize switch + { + GameFontFamilyAndSize.Undefined => 0, + GameFontFamilyAndSize.Axis96 => 9.6f, + GameFontFamilyAndSize.Axis12 => 12, + GameFontFamilyAndSize.Axis14 => 14, + GameFontFamilyAndSize.Axis18 => 18, + GameFontFamilyAndSize.Axis36 => 36, + GameFontFamilyAndSize.Jupiter16 => 16, + GameFontFamilyAndSize.Jupiter20 => 20, + GameFontFamilyAndSize.Jupiter23 => 23, + GameFontFamilyAndSize.Jupiter45 => 45, + GameFontFamilyAndSize.Jupiter46 => 46, + GameFontFamilyAndSize.Jupiter90 => 90, + GameFontFamilyAndSize.Meidinger16 => 16, + GameFontFamilyAndSize.Meidinger20 => 20, + GameFontFamilyAndSize.Meidinger40 => 40, + GameFontFamilyAndSize.MiedingerMid10 => 10, + GameFontFamilyAndSize.MiedingerMid12 => 12, + GameFontFamilyAndSize.MiedingerMid14 => 14, + GameFontFamilyAndSize.MiedingerMid18 => 18, + GameFontFamilyAndSize.MiedingerMid36 => 36, + GameFontFamilyAndSize.TrumpGothic184 => 18.4f, + GameFontFamilyAndSize.TrumpGothic23 => 23, + GameFontFamilyAndSize.TrumpGothic34 => 34, + GameFontFamilyAndSize.TrumpGothic68 => 8, + _ => throw new InvalidOperationException(), + }; + + /// + /// Gets the base font size in pixel unit. + /// + public float BaseSizePx => this.BaseSizePt * 4 / 3; + + /// + /// Gets or sets a value indicating whether this font is bold. + /// + public bool Bold + { + get => this.Weight > 0f; + set => this.Weight = value ? 1f : 0f; + } + + /// + /// Gets or sets a value indicating whether this font is italic. + /// + public bool Italic + { + get => this.SkewStrength != 0; + set => this.SkewStrength = value ? this.SizePx / 7 : 0; + } + + /// + /// Gets the recommend GameFontFamilyAndSize given family and size. + /// + /// Font family. + /// Font size in points. + /// Recommended GameFontFamilyAndSize. + public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size) + { + if (size <= 0) + return GameFontFamilyAndSize.Undefined; + + switch (family) { - this.FamilyAndSize = GetRecommendedFamilyAndSize(family, sizePx * 3 / 4); - this.Weight = this.SkewStrength = 0f; - this.SizePx = sizePx; - } - - /// - /// Initializes a new instance of the struct. - /// - /// Font family and size. - public GameFontStyle(GameFontFamilyAndSize familyAndSize) - { - this.FamilyAndSize = familyAndSize; - this.Weight = this.SkewStrength = 0f; - - // Dummy assignment to satisfy requirements - this.SizePx = 0; - - this.SizePx = this.BaseSizePx; - } - - /// - /// Gets or sets the size of the font in points unit. - /// - public float SizePt - { - get => this.SizePx * 3 / 4; - set => this.SizePx = value * 4 / 3; - } - - /// - /// Gets or sets the base skew strength. - /// - public float BaseSkewStrength - { - get => this.SkewStrength * this.BaseSizePx / this.SizePx; - set => this.SkewStrength = value * this.SizePx / this.BaseSizePx; - } - - /// - /// Gets the font family. - /// - public GameFontFamily Family => this.FamilyAndSize switch - { - GameFontFamilyAndSize.Undefined => GameFontFamily.Undefined, - GameFontFamilyAndSize.Axis96 => GameFontFamily.Axis, - GameFontFamilyAndSize.Axis12 => GameFontFamily.Axis, - GameFontFamilyAndSize.Axis14 => GameFontFamily.Axis, - GameFontFamilyAndSize.Axis18 => GameFontFamily.Axis, - GameFontFamilyAndSize.Axis36 => GameFontFamily.Axis, - GameFontFamilyAndSize.Jupiter16 => GameFontFamily.Jupiter, - GameFontFamilyAndSize.Jupiter20 => GameFontFamily.Jupiter, - GameFontFamilyAndSize.Jupiter23 => GameFontFamily.Jupiter, - GameFontFamilyAndSize.Jupiter45 => GameFontFamily.JupiterNumeric, - GameFontFamilyAndSize.Jupiter46 => GameFontFamily.Jupiter, - GameFontFamilyAndSize.Jupiter90 => GameFontFamily.JupiterNumeric, - GameFontFamilyAndSize.Meidinger16 => GameFontFamily.Meidinger, - GameFontFamilyAndSize.Meidinger20 => GameFontFamily.Meidinger, - GameFontFamilyAndSize.Meidinger40 => GameFontFamily.Meidinger, - GameFontFamilyAndSize.MiedingerMid10 => GameFontFamily.MiedingerMid, - GameFontFamilyAndSize.MiedingerMid12 => GameFontFamily.MiedingerMid, - GameFontFamilyAndSize.MiedingerMid14 => GameFontFamily.MiedingerMid, - GameFontFamilyAndSize.MiedingerMid18 => GameFontFamily.MiedingerMid, - GameFontFamilyAndSize.MiedingerMid36 => GameFontFamily.MiedingerMid, - GameFontFamilyAndSize.TrumpGothic184 => GameFontFamily.TrumpGothic, - GameFontFamilyAndSize.TrumpGothic23 => GameFontFamily.TrumpGothic, - GameFontFamilyAndSize.TrumpGothic34 => GameFontFamily.TrumpGothic, - GameFontFamilyAndSize.TrumpGothic68 => GameFontFamily.TrumpGothic, - _ => throw new InvalidOperationException(), - }; - - /// - /// Gets the corresponding GameFontFamilyAndSize but with minimum possible font sizes. - /// - public GameFontFamilyAndSize FamilyWithMinimumSize => this.Family switch - { - GameFontFamily.Axis => GameFontFamilyAndSize.Axis96, - GameFontFamily.Jupiter => GameFontFamilyAndSize.Jupiter16, - GameFontFamily.JupiterNumeric => GameFontFamilyAndSize.Jupiter45, - GameFontFamily.Meidinger => GameFontFamilyAndSize.Meidinger16, - GameFontFamily.MiedingerMid => GameFontFamilyAndSize.MiedingerMid10, - GameFontFamily.TrumpGothic => GameFontFamilyAndSize.TrumpGothic184, - _ => GameFontFamilyAndSize.Undefined, - }; - - /// - /// Gets the base font size in point unit. - /// - public float BaseSizePt => this.FamilyAndSize switch - { - GameFontFamilyAndSize.Undefined => 0, - GameFontFamilyAndSize.Axis96 => 9.6f, - GameFontFamilyAndSize.Axis12 => 12, - GameFontFamilyAndSize.Axis14 => 14, - GameFontFamilyAndSize.Axis18 => 18, - GameFontFamilyAndSize.Axis36 => 36, - GameFontFamilyAndSize.Jupiter16 => 16, - GameFontFamilyAndSize.Jupiter20 => 20, - GameFontFamilyAndSize.Jupiter23 => 23, - GameFontFamilyAndSize.Jupiter45 => 45, - GameFontFamilyAndSize.Jupiter46 => 46, - GameFontFamilyAndSize.Jupiter90 => 90, - GameFontFamilyAndSize.Meidinger16 => 16, - GameFontFamilyAndSize.Meidinger20 => 20, - GameFontFamilyAndSize.Meidinger40 => 40, - GameFontFamilyAndSize.MiedingerMid10 => 10, - GameFontFamilyAndSize.MiedingerMid12 => 12, - GameFontFamilyAndSize.MiedingerMid14 => 14, - GameFontFamilyAndSize.MiedingerMid18 => 18, - GameFontFamilyAndSize.MiedingerMid36 => 36, - GameFontFamilyAndSize.TrumpGothic184 => 18.4f, - GameFontFamilyAndSize.TrumpGothic23 => 23, - GameFontFamilyAndSize.TrumpGothic34 => 34, - GameFontFamilyAndSize.TrumpGothic68 => 8, - _ => throw new InvalidOperationException(), - }; - - /// - /// Gets the base font size in pixel unit. - /// - public float BaseSizePx => this.BaseSizePt * 4 / 3; - - /// - /// Gets or sets a value indicating whether this font is bold. - /// - public bool Bold - { - get => this.Weight > 0f; - set => this.Weight = value ? 1f : 0f; - } - - /// - /// Gets or sets a value indicating whether this font is italic. - /// - public bool Italic - { - get => this.SkewStrength != 0; - set => this.SkewStrength = value ? this.SizePx / 7 : 0; - } - - /// - /// Gets the recommend GameFontFamilyAndSize given family and size. - /// - /// Font family. - /// Font size in points. - /// Recommended GameFontFamilyAndSize. - public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size) - { - if (size <= 0) + case GameFontFamily.Undefined: return GameFontFamilyAndSize.Undefined; - switch (family) - { - case GameFontFamily.Undefined: - return GameFontFamilyAndSize.Undefined; + case GameFontFamily.Axis: + if (size <= 9.601) + return GameFontFamilyAndSize.Axis96; + else if (size <= 12.001) + return GameFontFamilyAndSize.Axis12; + else if (size <= 14.001) + return GameFontFamilyAndSize.Axis14; + else if (size <= 18.001) + return GameFontFamilyAndSize.Axis18; + else + return GameFontFamilyAndSize.Axis36; - case GameFontFamily.Axis: - if (size <= 9.601) - return GameFontFamilyAndSize.Axis96; - else if (size <= 12.001) - return GameFontFamilyAndSize.Axis12; - else if (size <= 14.001) - return GameFontFamilyAndSize.Axis14; - else if (size <= 18.001) - return GameFontFamilyAndSize.Axis18; - else - return GameFontFamilyAndSize.Axis36; + case GameFontFamily.Jupiter: + if (size <= 16.001) + return GameFontFamilyAndSize.Jupiter16; + else if (size <= 20.001) + return GameFontFamilyAndSize.Jupiter20; + else if (size <= 23.001) + return GameFontFamilyAndSize.Jupiter23; + else + return GameFontFamilyAndSize.Jupiter46; - case GameFontFamily.Jupiter: - if (size <= 16.001) - return GameFontFamilyAndSize.Jupiter16; - else if (size <= 20.001) - return GameFontFamilyAndSize.Jupiter20; - else if (size <= 23.001) - return GameFontFamilyAndSize.Jupiter23; - else - return GameFontFamilyAndSize.Jupiter46; + case GameFontFamily.JupiterNumeric: + if (size <= 45.001) + return GameFontFamilyAndSize.Jupiter45; + else + return GameFontFamilyAndSize.Jupiter90; - case GameFontFamily.JupiterNumeric: - if (size <= 45.001) - return GameFontFamilyAndSize.Jupiter45; - else - return GameFontFamilyAndSize.Jupiter90; + case GameFontFamily.Meidinger: + if (size <= 16.001) + return GameFontFamilyAndSize.Meidinger16; + else if (size <= 20.001) + return GameFontFamilyAndSize.Meidinger20; + else + return GameFontFamilyAndSize.Meidinger40; - case GameFontFamily.Meidinger: - if (size <= 16.001) - return GameFontFamilyAndSize.Meidinger16; - else if (size <= 20.001) - return GameFontFamilyAndSize.Meidinger20; - else - return GameFontFamilyAndSize.Meidinger40; + case GameFontFamily.MiedingerMid: + if (size <= 10.001) + return GameFontFamilyAndSize.MiedingerMid10; + else if (size <= 12.001) + return GameFontFamilyAndSize.MiedingerMid12; + else if (size <= 14.001) + return GameFontFamilyAndSize.MiedingerMid14; + else if (size <= 18.001) + return GameFontFamilyAndSize.MiedingerMid18; + else + return GameFontFamilyAndSize.MiedingerMid36; - case GameFontFamily.MiedingerMid: - if (size <= 10.001) - return GameFontFamilyAndSize.MiedingerMid10; - else if (size <= 12.001) - return GameFontFamilyAndSize.MiedingerMid12; - else if (size <= 14.001) - return GameFontFamilyAndSize.MiedingerMid14; - else if (size <= 18.001) - return GameFontFamilyAndSize.MiedingerMid18; - else - return GameFontFamilyAndSize.MiedingerMid36; + case GameFontFamily.TrumpGothic: + if (size <= 18.401) + return GameFontFamilyAndSize.TrumpGothic184; + else if (size <= 23.001) + return GameFontFamilyAndSize.TrumpGothic23; + else if (size <= 34.001) + return GameFontFamilyAndSize.TrumpGothic34; + else + return GameFontFamilyAndSize.TrumpGothic68; - case GameFontFamily.TrumpGothic: - if (size <= 18.401) - return GameFontFamilyAndSize.TrumpGothic184; - else if (size <= 23.001) - return GameFontFamilyAndSize.TrumpGothic23; - else if (size <= 34.001) - return GameFontFamilyAndSize.TrumpGothic34; - else - return GameFontFamilyAndSize.TrumpGothic68; - - default: - return GameFontFamilyAndSize.Undefined; - } - } - - /// - /// Calculates the adjustment to width resulting fron Weight and SkewStrength. - /// - /// Font information. - /// Glyph. - /// Width adjustment in pixel unit. - public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) - { - var widthDelta = this.Weight; - if (this.BaseSkewStrength > 0) - widthDelta += 1f * this.BaseSkewStrength * (reader.FontHeader.LineHeight - glyph.CurrentOffsetY) / reader.FontHeader.LineHeight; - else if (this.BaseSkewStrength < 0) - widthDelta -= 1f * this.BaseSkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight) / reader.FontHeader.LineHeight; - - return (int)Math.Ceiling(widthDelta); - } - - /// - public override string ToString() - { - return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})"; + default: + return GameFontFamilyAndSize.Undefined; } } + + /// + /// Calculates the adjustment to width resulting fron Weight and SkewStrength. + /// + /// Font information. + /// Glyph. + /// Width adjustment in pixel unit. + public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) + { + var widthDelta = this.Weight; + if (this.BaseSkewStrength > 0) + widthDelta += 1f * this.BaseSkewStrength * (reader.FontHeader.LineHeight - glyph.CurrentOffsetY) / reader.FontHeader.LineHeight; + else if (this.BaseSkewStrength < 0) + widthDelta -= 1f * this.BaseSkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight) / reader.FontHeader.LineHeight; + + return (int)Math.Ceiling(widthDelta); + } + + /// + public override string ToString() + { + return $"GameFontStyle({this.FamilyAndSize}, {this.SizePt}pt, skew={this.SkewStrength}, weight={this.Weight})"; + } } diff --git a/Dalamud/Interface/GlyphRangesJapanese.cs b/Dalamud/Interface/GlyphRangesJapanese.cs index 3c44492ff..2773b9db5 100644 --- a/Dalamud/Interface/GlyphRangesJapanese.cs +++ b/Dalamud/Interface/GlyphRangesJapanese.cs @@ -1,529 +1,528 @@ -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, - 0x223D, 0x223D, 0x2252, 0x2252, 0x2260, 0x2261, 0x2266, 0x2267, 0x226A, 0x226B, 0x2282, 0x2283, 0x2286, 0x2287, 0x22A5, 0x22A5, - 0x2312, 0x2312, 0x2500, 0x2503, 0x250C, 0x250C, 0x250F, 0x2510, 0x2513, 0x2514, 0x2517, 0x2518, 0x251B, 0x251D, 0x2520, 0x2520, - 0x2523, 0x2525, 0x2528, 0x2528, 0x252B, 0x252C, 0x252F, 0x2530, 0x2533, 0x2534, 0x2537, 0x2538, 0x253B, 0x253C, 0x253F, 0x253F, - 0x2542, 0x2542, 0x254B, 0x254B, 0x25A0, 0x25A1, 0x25B2, 0x25B3, 0x25BC, 0x25BD, 0x25C6, 0x25C7, 0x25CB, 0x25CB, 0x25CE, 0x25CF, - 0x25EF, 0x25EF, 0x2605, 0x2606, 0x2640, 0x2640, 0x2642, 0x2642, 0x266A, 0x266A, 0x266D, 0x266D, 0x266F, 0x266F, 0x3000, 0x30FF, - 0x31F0, 0x31FF, 0x4E00, 0x4E01, 0x4E03, 0x4E03, - 0x4E07, 0x4E0B, 0x4E0D, 0x4E0E, 0x4E10, 0x4E11, 0x4E14, 0x4E19, 0x4E1E, 0x4E1E, 0x4E21, 0x4E21, 0x4E26, 0x4E26, 0x4E2A, 0x4E2A, - 0x4E2D, 0x4E2D, 0x4E31, 0x4E32, 0x4E36, 0x4E36, 0x4E38, 0x4E39, 0x4E3B, 0x4E3C, 0x4E3F, 0x4E3F, 0x4E42, 0x4E43, 0x4E45, 0x4E45, - 0x4E4B, 0x4E4B, 0x4E4D, 0x4E4F, 0x4E55, 0x4E59, 0x4E5D, 0x4E5F, 0x4E62, 0x4E62, 0x4E71, 0x4E71, 0x4E73, 0x4E73, 0x4E7E, 0x4E7E, - 0x4E80, 0x4E80, 0x4E82, 0x4E82, 0x4E85, 0x4E86, 0x4E88, 0x4E8C, 0x4E8E, 0x4E8E, 0x4E91, 0x4E92, 0x4E94, 0x4E95, 0x4E98, 0x4E99, - 0x4E9B, 0x4E9C, 0x4E9E, 0x4EA2, 0x4EA4, 0x4EA6, 0x4EA8, 0x4EA8, 0x4EAB, 0x4EAE, 0x4EB0, 0x4EB0, 0x4EB3, 0x4EB3, 0x4EB6, 0x4EB6, - 0x4EBA, 0x4EBA, 0x4EC0, 0x4EC2, 0x4EC4, 0x4EC4, 0x4EC6, 0x4EC7, 0x4ECA, 0x4ECB, 0x4ECD, 0x4ECF, 0x4ED4, 0x4ED9, 0x4EDD, 0x4EDF, - 0x4EE3, 0x4EE5, 0x4EED, 0x4EEE, 0x4EF0, 0x4EF0, 0x4EF2, 0x4EF2, 0x4EF6, 0x4EF7, 0x4EFB, 0x4EFB, 0x4F01, 0x4F01, 0x4F09, 0x4F0A, - 0x4F0D, 0x4F11, 0x4F1A, 0x4F1A, 0x4F1C, 0x4F1D, 0x4F2F, 0x4F30, 0x4F34, 0x4F34, 0x4F36, 0x4F36, 0x4F38, 0x4F38, 0x4F3A, 0x4F3A, - 0x4F3C, 0x4F3D, 0x4F43, 0x4F43, 0x4F46, 0x4F47, 0x4F4D, 0x4F51, 0x4F53, 0x4F53, 0x4F55, 0x4F55, 0x4F57, 0x4F57, 0x4F59, 0x4F5E, - 0x4F69, 0x4F69, 0x4F6F, 0x4F70, 0x4F73, 0x4F73, 0x4F75, 0x4F76, 0x4F7B, 0x4F7C, 0x4F7F, 0x4F7F, 0x4F83, 0x4F83, 0x4F86, 0x4F86, - 0x4F88, 0x4F88, 0x4F8B, 0x4F8B, 0x4F8D, 0x4F8D, 0x4F8F, 0x4F8F, 0x4F91, 0x4F91, 0x4F96, 0x4F96, 0x4F98, 0x4F98, 0x4F9B, 0x4F9B, - 0x4F9D, 0x4F9D, 0x4FA0, 0x4FA1, 0x4FAB, 0x4FAB, 0x4FAD, 0x4FAF, 0x4FB5, 0x4FB6, 0x4FBF, 0x4FBF, 0x4FC2, 0x4FC4, 0x4FCA, 0x4FCA, - 0x4FCE, 0x4FCE, 0x4FD0, 0x4FD1, 0x4FD4, 0x4FD4, 0x4FD7, 0x4FD8, 0x4FDA, 0x4FDB, 0x4FDD, 0x4FDD, 0x4FDF, 0x4FDF, 0x4FE1, 0x4FE1, - 0x4FE3, 0x4FE5, 0x4FEE, 0x4FEF, 0x4FF3, 0x4FF3, 0x4FF5, 0x4FF6, 0x4FF8, 0x4FF8, 0x4FFA, 0x4FFA, 0x4FFE, 0x4FFE, 0x5005, 0x5006, - 0x5009, 0x5009, 0x500B, 0x500B, 0x500D, 0x500D, 0x500F, 0x500F, 0x5011, 0x5012, 0x5014, 0x5014, 0x5016, 0x5016, 0x5019, 0x501A, - 0x501F, 0x501F, 0x5021, 0x5021, 0x5023, 0x5026, 0x5028, 0x502D, 0x5036, 0x5036, 0x5039, 0x5039, 0x5043, 0x5043, 0x5047, 0x5049, - 0x504F, 0x5050, 0x5055, 0x5056, 0x505A, 0x505A, 0x505C, 0x505C, 0x5065, 0x5065, 0x506C, 0x506C, 0x5072, 0x5072, 0x5074, 0x5076, - 0x5078, 0x5078, 0x507D, 0x507D, 0x5080, 0x5080, 0x5085, 0x5085, 0x508D, 0x508D, 0x5091, 0x5091, 0x5098, 0x509A, 0x50AC, 0x50AD, - 0x50B2, 0x50B5, 0x50B7, 0x50B7, 0x50BE, 0x50BE, 0x50C2, 0x50C2, 0x50C5, 0x50C5, 0x50C9, 0x50CA, 0x50CD, 0x50CD, 0x50CF, 0x50CF, - 0x50D1, 0x50D1, 0x50D5, 0x50D6, 0x50DA, 0x50DA, 0x50DE, 0x50DE, 0x50E3, 0x50E3, 0x50E5, 0x50E5, 0x50E7, 0x50E7, 0x50ED, 0x50EE, - 0x50F5, 0x50F5, 0x50F9, 0x50F9, 0x50FB, 0x50FB, 0x5100, 0x5102, 0x5104, 0x5104, 0x5109, 0x5109, 0x5112, 0x5112, 0x5114, 0x5116, - 0x5118, 0x5118, 0x511A, 0x511A, 0x511F, 0x511F, 0x5121, 0x5121, 0x512A, 0x512A, 0x5132, 0x5132, 0x5137, 0x5137, 0x513A, 0x513C, - 0x513F, 0x5141, 0x5143, 0x5149, 0x514B, 0x514E, 0x5150, 0x5150, 0x5152, 0x5152, 0x5154, 0x5154, 0x515A, 0x515A, 0x515C, 0x515C, - 0x5162, 0x5162, 0x5165, 0x5165, 0x5168, 0x516E, 0x5171, 0x5171, 0x5175, 0x5178, 0x517C, 0x517C, 0x5180, 0x5180, 0x5182, 0x5182, - 0x5185, 0x5186, 0x5189, 0x518A, 0x518C, 0x518D, 0x518F, 0x5193, 0x5195, 0x5197, 0x5199, 0x5199, 0x51A0, 0x51A0, 0x51A2, 0x51A2, - 0x51A4, 0x51A6, 0x51A8, 0x51AC, 0x51B0, 0x51B7, 0x51BD, 0x51BD, 0x51C4, 0x51C6, 0x51C9, 0x51C9, 0x51CB, 0x51CD, 0x51D6, 0x51D6, - 0x51DB, 0x51DD, 0x51E0, 0x51E1, 0x51E6, 0x51E7, 0x51E9, 0x51EA, 0x51ED, 0x51ED, 0x51F0, 0x51F1, 0x51F5, 0x51F6, 0x51F8, 0x51FA, - 0x51FD, 0x51FE, 0x5200, 0x5200, 0x5203, 0x5204, 0x5206, 0x5208, 0x520A, 0x520B, 0x520E, 0x520E, 0x5211, 0x5211, 0x5214, 0x5214, - 0x5217, 0x5217, 0x521D, 0x521D, 0x5224, 0x5225, 0x5227, 0x5227, 0x5229, 0x522A, 0x522E, 0x522E, 0x5230, 0x5230, 0x5233, 0x5233, - 0x5236, 0x523B, 0x5243, 0x5244, 0x5247, 0x5247, 0x524A, 0x524D, 0x524F, 0x524F, 0x5254, 0x5254, 0x5256, 0x5256, 0x525B, 0x525B, - 0x525E, 0x525E, 0x5263, 0x5265, 0x5269, 0x526A, 0x526F, 0x5275, 0x527D, 0x527D, 0x527F, 0x527F, 0x5283, 0x5283, 0x5287, 0x5289, - 0x528D, 0x528D, 0x5291, 0x5292, 0x5294, 0x5294, 0x529B, 0x529B, 0x529F, 0x52A0, 0x52A3, 0x52A3, 0x52A9, 0x52AD, 0x52B1, 0x52B1, - 0x52B4, 0x52B5, 0x52B9, 0x52B9, 0x52BC, 0x52BC, 0x52BE, 0x52BE, 0x52C1, 0x52C1, 0x52C3, 0x52C3, 0x52C5, 0x52C5, 0x52C7, 0x52C7, - 0x52C9, 0x52C9, 0x52CD, 0x52CD, 0x52D2, 0x52D2, 0x52D5, 0x52D5, 0x52D7, 0x52D9, 0x52DD, 0x52E0, 0x52E2, 0x52E4, 0x52E6, 0x52E7, - 0x52F2, 0x52F3, 0x52F5, 0x52F5, 0x52F8, 0x52FA, 0x52FE, 0x52FF, 0x5301, 0x5302, 0x5305, 0x5306, 0x5308, 0x5308, 0x530D, 0x530D, - 0x530F, 0x5310, 0x5315, 0x5317, 0x5319, 0x531A, 0x531D, 0x531D, 0x5320, 0x5321, 0x5323, 0x5323, 0x532A, 0x532A, 0x532F, 0x532F, - 0x5331, 0x5331, 0x5333, 0x5333, 0x5338, 0x533B, 0x533F, 0x5341, 0x5343, 0x5343, 0x5345, 0x534A, 0x534D, 0x534D, 0x5351, 0x5354, - 0x5357, 0x5358, 0x535A, 0x535A, 0x535C, 0x535C, 0x535E, 0x535E, 0x5360, 0x5360, 0x5366, 0x5366, 0x5369, 0x5369, 0x536E, 0x5371, - 0x5373, 0x5375, 0x5377, 0x5378, 0x537B, 0x537B, 0x537F, 0x537F, 0x5382, 0x5382, 0x5384, 0x5384, 0x5396, 0x5396, 0x5398, 0x5398, - 0x539A, 0x539A, 0x539F, 0x53A0, 0x53A5, 0x53A6, 0x53A8, 0x53A9, 0x53AD, 0x53AE, 0x53B0, 0x53B0, 0x53B3, 0x53B3, 0x53B6, 0x53B6, - 0x53BB, 0x53BB, 0x53C2, 0x53C3, 0x53C8, 0x53CE, 0x53D4, 0x53D4, 0x53D6, 0x53D7, 0x53D9, 0x53D9, 0x53DB, 0x53DB, 0x53DF, 0x53DF, - 0x53E1, 0x53E5, 0x53E8, 0x53F3, 0x53F6, 0x53F8, 0x53FA, 0x53FA, 0x5401, 0x5401, 0x5403, 0x5404, 0x5408, 0x5411, 0x541B, 0x541B, - 0x541D, 0x541D, 0x541F, 0x5420, 0x5426, 0x5426, 0x5429, 0x5429, 0x542B, 0x542E, 0x5436, 0x5436, 0x5438, 0x5439, 0x543B, 0x543E, - 0x5440, 0x5440, 0x5442, 0x5442, 0x5446, 0x5446, 0x5448, 0x544A, 0x544E, 0x544E, 0x5451, 0x5451, 0x545F, 0x545F, 0x5468, 0x5468, - 0x546A, 0x546A, 0x5470, 0x5471, 0x5473, 0x5473, 0x5475, 0x5477, 0x547B, 0x547D, 0x5480, 0x5480, 0x5484, 0x5484, 0x5486, 0x5486, - 0x548B, 0x548C, 0x548E, 0x5490, 0x5492, 0x5492, 0x54A2, 0x54A2, 0x54A4, 0x54A5, 0x54A8, 0x54A8, 0x54AB, 0x54AC, 0x54AF, 0x54AF, - 0x54B2, 0x54B3, 0x54B8, 0x54B8, 0x54BC, 0x54BE, 0x54C0, 0x54C2, 0x54C4, 0x54C4, 0x54C7, 0x54C9, 0x54D8, 0x54D8, 0x54E1, 0x54E2, - 0x54E5, 0x54E6, 0x54E8, 0x54E9, 0x54ED, 0x54EE, 0x54F2, 0x54F2, 0x54FA, 0x54FA, 0x54FD, 0x54FD, 0x5504, 0x5504, 0x5506, 0x5507, - 0x550F, 0x5510, 0x5514, 0x5514, 0x5516, 0x5516, 0x552E, 0x552F, 0x5531, 0x5531, 0x5533, 0x5533, 0x5538, 0x5539, 0x553E, 0x553E, - 0x5540, 0x5540, 0x5544, 0x5546, 0x554C, 0x554C, 0x554F, 0x554F, 0x5553, 0x5553, 0x5556, 0x5557, 0x555C, 0x555D, 0x5563, 0x5563, - 0x557B, 0x557C, 0x557E, 0x557E, 0x5580, 0x5580, 0x5583, 0x5584, 0x5587, 0x5587, 0x5589, 0x558B, 0x5598, 0x559A, 0x559C, 0x559F, - 0x55A7, 0x55AC, 0x55AE, 0x55AE, 0x55B0, 0x55B0, 0x55B6, 0x55B6, 0x55C4, 0x55C5, 0x55C7, 0x55C7, 0x55D4, 0x55D4, 0x55DA, 0x55DA, - 0x55DC, 0x55DC, 0x55DF, 0x55DF, 0x55E3, 0x55E4, 0x55F7, 0x55F7, 0x55F9, 0x55F9, 0x55FD, 0x55FE, 0x5606, 0x5606, 0x5609, 0x5609, - 0x5614, 0x5614, 0x5616, 0x5618, 0x561B, 0x561B, 0x5629, 0x5629, 0x562F, 0x562F, 0x5631, 0x5632, 0x5634, 0x5634, 0x5636, 0x5636, - 0x5638, 0x5638, 0x5642, 0x5642, 0x564C, 0x564C, 0x564E, 0x564E, 0x5650, 0x5650, 0x565B, 0x565B, 0x5664, 0x5664, 0x5668, 0x5668, - 0x566A, 0x566C, 0x5674, 0x5674, 0x5678, 0x5678, 0x567A, 0x567A, 0x5680, 0x5680, 0x5686, 0x5687, 0x568A, 0x568A, 0x568F, 0x568F, - 0x5694, 0x5694, 0x56A0, 0x56A0, 0x56A2, 0x56A2, 0x56A5, 0x56A5, 0x56AE, 0x56AE, 0x56B4, 0x56B4, 0x56B6, 0x56B6, 0x56BC, 0x56BC, - 0x56C0, 0x56C3, 0x56C8, 0x56C8, 0x56CE, 0x56CE, 0x56D1, 0x56D1, 0x56D3, 0x56D3, 0x56D7, 0x56D8, 0x56DA, 0x56DB, 0x56DE, 0x56DE, - 0x56E0, 0x56E0, 0x56E3, 0x56E3, 0x56EE, 0x56EE, 0x56F0, 0x56F0, 0x56F2, 0x56F3, 0x56F9, 0x56FA, 0x56FD, 0x56FD, 0x56FF, 0x5700, - 0x5703, 0x5704, 0x5708, 0x5709, 0x570B, 0x570B, 0x570D, 0x570D, 0x570F, 0x570F, 0x5712, 0x5713, 0x5716, 0x5716, 0x5718, 0x5718, - 0x571C, 0x571C, 0x571F, 0x571F, 0x5726, 0x5728, 0x572D, 0x572D, 0x5730, 0x5730, 0x5737, 0x5738, 0x573B, 0x573B, 0x5740, 0x5740, - 0x5742, 0x5742, 0x5747, 0x5747, 0x574A, 0x574A, 0x574E, 0x5751, 0x5761, 0x5761, 0x5764, 0x5764, 0x5766, 0x5766, 0x5769, 0x576A, - 0x577F, 0x577F, 0x5782, 0x5782, 0x5788, 0x5789, 0x578B, 0x578B, 0x5793, 0x5793, 0x57A0, 0x57A0, 0x57A2, 0x57A4, 0x57AA, 0x57AA, - 0x57B0, 0x57B0, 0x57B3, 0x57B3, 0x57C0, 0x57C0, 0x57C3, 0x57C3, 0x57C6, 0x57C6, 0x57CB, 0x57CB, 0x57CE, 0x57CE, 0x57D2, 0x57D4, - 0x57D6, 0x57D6, 0x57DC, 0x57DC, 0x57DF, 0x57E0, 0x57E3, 0x57E3, 0x57F4, 0x57F4, 0x57F7, 0x57F7, 0x57F9, 0x57FA, 0x57FC, 0x57FC, - 0x5800, 0x5800, 0x5802, 0x5802, 0x5805, 0x5806, 0x580A, 0x580B, 0x5815, 0x5815, 0x5819, 0x5819, 0x581D, 0x581D, 0x5821, 0x5821, - 0x5824, 0x5824, 0x582A, 0x582A, 0x582F, 0x5831, 0x5834, 0x5835, 0x583A, 0x583A, 0x583D, 0x583D, 0x5840, 0x5841, 0x584A, 0x584B, - 0x5851, 0x5852, 0x5854, 0x5854, 0x5857, 0x585A, 0x585E, 0x585E, 0x5862, 0x5862, 0x5869, 0x5869, 0x586B, 0x586B, 0x5870, 0x5870, - 0x5872, 0x5872, 0x5875, 0x5875, 0x5879, 0x5879, 0x587E, 0x587E, 0x5883, 0x5883, 0x5885, 0x5885, 0x5893, 0x5893, 0x5897, 0x5897, - 0x589C, 0x589C, 0x589F, 0x589F, 0x58A8, 0x58A8, 0x58AB, 0x58AB, 0x58AE, 0x58AE, 0x58B3, 0x58B3, 0x58B8, 0x58BB, 0x58BE, 0x58BE, - 0x58C1, 0x58C1, 0x58C5, 0x58C5, 0x58C7, 0x58C7, 0x58CA, 0x58CA, 0x58CC, 0x58CC, 0x58D1, 0x58D1, 0x58D3, 0x58D3, 0x58D5, 0x58D5, - 0x58D7, 0x58D9, 0x58DC, 0x58DC, 0x58DE, 0x58DF, 0x58E4, 0x58E5, 0x58EB, 0x58EC, 0x58EE, 0x58F2, 0x58F7, 0x58F7, 0x58F9, 0x58FD, - 0x5902, 0x5902, 0x5909, 0x590A, 0x590F, 0x5910, 0x5915, 0x5916, 0x5918, 0x591C, 0x5922, 0x5922, 0x5925, 0x5925, 0x5927, 0x5927, - 0x5929, 0x592E, 0x5931, 0x5932, 0x5937, 0x5938, 0x593E, 0x593E, 0x5944, 0x5944, 0x5947, 0x5949, 0x594E, 0x5951, 0x5954, 0x5955, - 0x5957, 0x5958, 0x595A, 0x595A, 0x5960, 0x5960, 0x5962, 0x5962, 0x5965, 0x5965, 0x5967, 0x596A, 0x596C, 0x596C, 0x596E, 0x596E, - 0x5973, 0x5974, 0x5978, 0x5978, 0x597D, 0x597D, 0x5981, 0x5984, 0x598A, 0x598A, 0x598D, 0x598D, 0x5993, 0x5993, 0x5996, 0x5996, - 0x5999, 0x5999, 0x599B, 0x599B, 0x599D, 0x599D, 0x59A3, 0x59A3, 0x59A5, 0x59A5, 0x59A8, 0x59A8, 0x59AC, 0x59AC, 0x59B2, 0x59B2, - 0x59B9, 0x59B9, 0x59BB, 0x59BB, 0x59BE, 0x59BE, 0x59C6, 0x59C6, 0x59C9, 0x59C9, 0x59CB, 0x59CB, 0x59D0, 0x59D1, 0x59D3, 0x59D4, - 0x59D9, 0x59DA, 0x59DC, 0x59DC, 0x59E5, 0x59E6, 0x59E8, 0x59E8, 0x59EA, 0x59EB, 0x59F6, 0x59F6, 0x59FB, 0x59FB, 0x59FF, 0x59FF, - 0x5A01, 0x5A01, 0x5A03, 0x5A03, 0x5A09, 0x5A09, 0x5A11, 0x5A11, 0x5A18, 0x5A18, 0x5A1A, 0x5A1A, 0x5A1C, 0x5A1C, 0x5A1F, 0x5A20, - 0x5A25, 0x5A25, 0x5A29, 0x5A29, 0x5A2F, 0x5A2F, 0x5A35, 0x5A36, 0x5A3C, 0x5A3C, 0x5A40, 0x5A41, 0x5A46, 0x5A46, 0x5A49, 0x5A49, - 0x5A5A, 0x5A5A, 0x5A62, 0x5A62, 0x5A66, 0x5A66, 0x5A6A, 0x5A6A, 0x5A6C, 0x5A6C, 0x5A7F, 0x5A7F, 0x5A92, 0x5A92, 0x5A9A, 0x5A9B, - 0x5ABC, 0x5ABE, 0x5AC1, 0x5AC2, 0x5AC9, 0x5AC9, 0x5ACB, 0x5ACC, 0x5AD0, 0x5AD0, 0x5AD6, 0x5AD7, 0x5AE1, 0x5AE1, 0x5AE3, 0x5AE3, - 0x5AE6, 0x5AE6, 0x5AE9, 0x5AE9, 0x5AFA, 0x5AFB, 0x5B09, 0x5B09, 0x5B0B, 0x5B0C, 0x5B16, 0x5B16, 0x5B22, 0x5B22, 0x5B2A, 0x5B2A, - 0x5B2C, 0x5B2C, 0x5B30, 0x5B30, 0x5B32, 0x5B32, 0x5B36, 0x5B36, 0x5B3E, 0x5B3E, 0x5B40, 0x5B40, 0x5B43, 0x5B43, 0x5B45, 0x5B45, - 0x5B50, 0x5B51, 0x5B54, 0x5B55, 0x5B57, 0x5B58, 0x5B5A, 0x5B5D, 0x5B5F, 0x5B5F, 0x5B63, 0x5B66, 0x5B69, 0x5B69, 0x5B6B, 0x5B6B, - 0x5B70, 0x5B71, 0x5B73, 0x5B73, 0x5B75, 0x5B75, 0x5B78, 0x5B78, 0x5B7A, 0x5B7A, 0x5B80, 0x5B80, 0x5B83, 0x5B83, 0x5B85, 0x5B85, - 0x5B87, 0x5B89, 0x5B8B, 0x5B8D, 0x5B8F, 0x5B8F, 0x5B95, 0x5B95, 0x5B97, 0x5B9D, 0x5B9F, 0x5B9F, 0x5BA2, 0x5BA6, 0x5BAE, 0x5BAE, - 0x5BB0, 0x5BB0, 0x5BB3, 0x5BB6, 0x5BB8, 0x5BB9, 0x5BBF, 0x5BBF, 0x5BC2, 0x5BC7, 0x5BC9, 0x5BC9, 0x5BCC, 0x5BCC, 0x5BD0, 0x5BD0, - 0x5BD2, 0x5BD4, 0x5BDB, 0x5BDB, 0x5BDD, 0x5BDF, 0x5BE1, 0x5BE2, 0x5BE4, 0x5BE9, 0x5BEB, 0x5BEB, 0x5BEE, 0x5BEE, 0x5BF0, 0x5BF0, - 0x5BF3, 0x5BF3, 0x5BF5, 0x5BF6, 0x5BF8, 0x5BF8, 0x5BFA, 0x5BFA, 0x5BFE, 0x5BFF, 0x5C01, 0x5C02, 0x5C04, 0x5C0B, 0x5C0D, 0x5C0F, - 0x5C11, 0x5C11, 0x5C13, 0x5C13, 0x5C16, 0x5C16, 0x5C1A, 0x5C1A, 0x5C20, 0x5C20, 0x5C22, 0x5C22, 0x5C24, 0x5C24, 0x5C28, 0x5C28, - 0x5C2D, 0x5C2D, 0x5C31, 0x5C31, 0x5C38, 0x5C41, 0x5C45, 0x5C46, 0x5C48, 0x5C48, 0x5C4A, 0x5C4B, 0x5C4D, 0x5C51, 0x5C53, 0x5C53, - 0x5C55, 0x5C55, 0x5C5E, 0x5C5E, 0x5C60, 0x5C61, 0x5C64, 0x5C65, 0x5C6C, 0x5C6C, 0x5C6E, 0x5C6F, 0x5C71, 0x5C71, 0x5C76, 0x5C76, - 0x5C79, 0x5C79, 0x5C8C, 0x5C8C, 0x5C90, 0x5C91, 0x5C94, 0x5C94, 0x5CA1, 0x5CA1, 0x5CA8, 0x5CA9, 0x5CAB, 0x5CAC, 0x5CB1, 0x5CB1, - 0x5CB3, 0x5CB3, 0x5CB6, 0x5CB8, 0x5CBB, 0x5CBC, 0x5CBE, 0x5CBE, 0x5CC5, 0x5CC5, 0x5CC7, 0x5CC7, 0x5CD9, 0x5CD9, 0x5CE0, 0x5CE1, - 0x5CE8, 0x5CEA, 0x5CED, 0x5CED, 0x5CEF, 0x5CF0, 0x5CF6, 0x5CF6, 0x5CFA, 0x5CFB, 0x5CFD, 0x5CFD, 0x5D07, 0x5D07, 0x5D0B, 0x5D0B, - 0x5D0E, 0x5D0E, 0x5D11, 0x5D11, 0x5D14, 0x5D1B, 0x5D1F, 0x5D1F, 0x5D22, 0x5D22, 0x5D29, 0x5D29, 0x5D4B, 0x5D4C, 0x5D4E, 0x5D4E, - 0x5D50, 0x5D50, 0x5D52, 0x5D52, 0x5D5C, 0x5D5C, 0x5D69, 0x5D69, 0x5D6C, 0x5D6C, 0x5D6F, 0x5D6F, 0x5D73, 0x5D73, 0x5D76, 0x5D76, - 0x5D82, 0x5D82, 0x5D84, 0x5D84, 0x5D87, 0x5D87, 0x5D8B, 0x5D8C, 0x5D90, 0x5D90, 0x5D9D, 0x5D9D, 0x5DA2, 0x5DA2, 0x5DAC, 0x5DAC, - 0x5DAE, 0x5DAE, 0x5DB7, 0x5DB7, 0x5DBA, 0x5DBA, 0x5DBC, 0x5DBD, 0x5DC9, 0x5DC9, 0x5DCC, 0x5DCD, 0x5DD2, 0x5DD3, 0x5DD6, 0x5DD6, - 0x5DDB, 0x5DDB, 0x5DDD, 0x5DDE, 0x5DE1, 0x5DE1, 0x5DE3, 0x5DE3, 0x5DE5, 0x5DE8, 0x5DEB, 0x5DEB, 0x5DEE, 0x5DEE, 0x5DF1, 0x5DF5, - 0x5DF7, 0x5DF7, 0x5DFB, 0x5DFB, 0x5DFD, 0x5DFE, 0x5E02, 0x5E03, 0x5E06, 0x5E06, 0x5E0B, 0x5E0C, 0x5E11, 0x5E11, 0x5E16, 0x5E16, - 0x5E19, 0x5E1B, 0x5E1D, 0x5E1D, 0x5E25, 0x5E25, 0x5E2B, 0x5E2B, 0x5E2D, 0x5E2D, 0x5E2F, 0x5E30, 0x5E33, 0x5E33, 0x5E36, 0x5E38, - 0x5E3D, 0x5E3D, 0x5E40, 0x5E40, 0x5E43, 0x5E45, 0x5E47, 0x5E47, 0x5E4C, 0x5E4C, 0x5E4E, 0x5E4E, 0x5E54, 0x5E55, 0x5E57, 0x5E57, - 0x5E5F, 0x5E5F, 0x5E61, 0x5E64, 0x5E72, 0x5E76, 0x5E78, 0x5E7F, 0x5E81, 0x5E81, 0x5E83, 0x5E84, 0x5E87, 0x5E87, 0x5E8A, 0x5E8A, - 0x5E8F, 0x5E8F, 0x5E95, 0x5E97, 0x5E9A, 0x5E9A, 0x5E9C, 0x5E9C, 0x5EA0, 0x5EA0, 0x5EA6, 0x5EA7, 0x5EAB, 0x5EAB, 0x5EAD, 0x5EAD, - 0x5EB5, 0x5EB8, 0x5EC1, 0x5EC3, 0x5EC8, 0x5ECA, 0x5ECF, 0x5ED0, 0x5ED3, 0x5ED3, 0x5ED6, 0x5ED6, 0x5EDA, 0x5EDB, 0x5EDD, 0x5EDD, - 0x5EDF, 0x5EE3, 0x5EE8, 0x5EE9, 0x5EEC, 0x5EEC, 0x5EF0, 0x5EF1, 0x5EF3, 0x5EF4, 0x5EF6, 0x5EF8, 0x5EFA, 0x5EFC, 0x5EFE, 0x5EFF, - 0x5F01, 0x5F01, 0x5F03, 0x5F04, 0x5F09, 0x5F0D, 0x5F0F, 0x5F11, 0x5F13, 0x5F18, 0x5F1B, 0x5F1B, 0x5F1F, 0x5F1F, 0x5F25, 0x5F27, - 0x5F29, 0x5F29, 0x5F2D, 0x5F2D, 0x5F2F, 0x5F2F, 0x5F31, 0x5F31, 0x5F35, 0x5F35, 0x5F37, 0x5F38, 0x5F3C, 0x5F3C, 0x5F3E, 0x5F3E, - 0x5F41, 0x5F41, 0x5F48, 0x5F48, 0x5F4A, 0x5F4A, 0x5F4C, 0x5F4C, 0x5F4E, 0x5F4E, 0x5F51, 0x5F51, 0x5F53, 0x5F53, 0x5F56, 0x5F57, - 0x5F59, 0x5F59, 0x5F5C, 0x5F5D, 0x5F61, 0x5F62, 0x5F66, 0x5F66, 0x5F69, 0x5F6D, 0x5F70, 0x5F71, 0x5F73, 0x5F73, 0x5F77, 0x5F77, - 0x5F79, 0x5F79, 0x5F7C, 0x5F7C, 0x5F7F, 0x5F85, 0x5F87, 0x5F88, 0x5F8A, 0x5F8C, 0x5F90, 0x5F93, 0x5F97, 0x5F99, 0x5F9E, 0x5F9E, - 0x5FA0, 0x5FA1, 0x5FA8, 0x5FAA, 0x5FAD, 0x5FAE, 0x5FB3, 0x5FB4, 0x5FB9, 0x5FB9, 0x5FBC, 0x5FBD, 0x5FC3, 0x5FC3, 0x5FC5, 0x5FC5, - 0x5FCC, 0x5FCD, 0x5FD6, 0x5FD9, 0x5FDC, 0x5FDD, 0x5FE0, 0x5FE0, 0x5FE4, 0x5FE4, 0x5FEB, 0x5FEB, 0x5FF0, 0x5FF1, 0x5FF5, 0x5FF5, - 0x5FF8, 0x5FF8, 0x5FFB, 0x5FFB, 0x5FFD, 0x5FFD, 0x5FFF, 0x5FFF, 0x600E, 0x6010, 0x6012, 0x6012, 0x6015, 0x6016, 0x6019, 0x6019, - 0x601B, 0x601D, 0x6020, 0x6021, 0x6025, 0x602B, 0x602F, 0x602F, 0x6031, 0x6031, 0x603A, 0x603A, 0x6041, 0x6043, 0x6046, 0x6046, - 0x604A, 0x604B, 0x604D, 0x604D, 0x6050, 0x6050, 0x6052, 0x6052, 0x6055, 0x6055, 0x6059, 0x605A, 0x605F, 0x6060, 0x6062, 0x6065, - 0x6068, 0x606D, 0x606F, 0x6070, 0x6075, 0x6075, 0x6077, 0x6077, 0x6081, 0x6081, 0x6083, 0x6084, 0x6089, 0x6089, 0x608B, 0x608D, - 0x6092, 0x6092, 0x6094, 0x6094, 0x6096, 0x6097, 0x609A, 0x609B, 0x609F, 0x60A0, 0x60A3, 0x60A3, 0x60A6, 0x60A7, 0x60A9, 0x60AA, - 0x60B2, 0x60B6, 0x60B8, 0x60B8, 0x60BC, 0x60BD, 0x60C5, 0x60C7, 0x60D1, 0x60D1, 0x60D3, 0x60D3, 0x60D8, 0x60D8, 0x60DA, 0x60DA, - 0x60DC, 0x60DC, 0x60DF, 0x60E1, 0x60E3, 0x60E3, 0x60E7, 0x60E8, 0x60F0, 0x60F1, 0x60F3, 0x60F4, 0x60F6, 0x60F7, 0x60F9, 0x60FB, - 0x6100, 0x6101, 0x6103, 0x6103, 0x6106, 0x6106, 0x6108, 0x6109, 0x610D, 0x610F, 0x6115, 0x6115, 0x611A, 0x611B, 0x611F, 0x611F, - 0x6121, 0x6121, 0x6127, 0x6128, 0x612C, 0x612C, 0x6134, 0x6134, 0x613C, 0x613F, 0x6142, 0x6142, 0x6144, 0x6144, 0x6147, 0x6148, - 0x614A, 0x614E, 0x6153, 0x6153, 0x6155, 0x6155, 0x6158, 0x615A, 0x615D, 0x615D, 0x615F, 0x615F, 0x6162, 0x6163, 0x6165, 0x6165, - 0x6167, 0x6168, 0x616B, 0x616B, 0x616E, 0x6171, 0x6173, 0x6177, 0x617E, 0x617E, 0x6182, 0x6182, 0x6187, 0x6187, 0x618A, 0x618A, - 0x618E, 0x618E, 0x6190, 0x6191, 0x6194, 0x6194, 0x6196, 0x6196, 0x6199, 0x619A, 0x61A4, 0x61A4, 0x61A7, 0x61A7, 0x61A9, 0x61A9, - 0x61AB, 0x61AC, 0x61AE, 0x61AE, 0x61B2, 0x61B2, 0x61B6, 0x61B6, 0x61BA, 0x61BA, 0x61BE, 0x61BE, 0x61C3, 0x61C3, 0x61C6, 0x61CD, - 0x61D0, 0x61D0, 0x61E3, 0x61E3, 0x61E6, 0x61E6, 0x61F2, 0x61F2, 0x61F4, 0x61F4, 0x61F6, 0x61F8, 0x61FA, 0x61FA, 0x61FC, 0x6200, - 0x6208, 0x620A, 0x620C, 0x620E, 0x6210, 0x6212, 0x6214, 0x6214, 0x6216, 0x6216, 0x621A, 0x621B, 0x621D, 0x621F, 0x6221, 0x6221, - 0x6226, 0x6226, 0x622A, 0x622A, 0x622E, 0x6230, 0x6232, 0x6234, 0x6238, 0x6238, 0x623B, 0x623B, 0x623F, 0x6241, 0x6247, 0x6249, - 0x624B, 0x624B, 0x624D, 0x624E, 0x6253, 0x6253, 0x6255, 0x6255, 0x6258, 0x6258, 0x625B, 0x625B, 0x625E, 0x625E, 0x6260, 0x6260, - 0x6263, 0x6263, 0x6268, 0x6268, 0x626E, 0x626E, 0x6271, 0x6271, 0x6276, 0x6276, 0x6279, 0x6279, 0x627C, 0x627C, 0x627E, 0x6280, - 0x6282, 0x6284, 0x6289, 0x628A, 0x6291, 0x6298, 0x629B, 0x629C, 0x629E, 0x629E, 0x62AB, 0x62AC, 0x62B1, 0x62B1, 0x62B5, 0x62B5, - 0x62B9, 0x62B9, 0x62BB, 0x62BD, 0x62C2, 0x62C2, 0x62C5, 0x62CA, 0x62CC, 0x62CD, 0x62CF, 0x62D4, 0x62D7, 0x62D9, 0x62DB, 0x62DD, - 0x62E0, 0x62E1, 0x62EC, 0x62EF, 0x62F1, 0x62F1, 0x62F3, 0x62F3, 0x62F5, 0x62F7, 0x62FE, 0x62FF, 0x6301, 0x6302, 0x6307, 0x6309, - 0x630C, 0x630C, 0x6311, 0x6311, 0x6319, 0x6319, 0x631F, 0x631F, 0x6327, 0x6328, 0x632B, 0x632B, 0x632F, 0x632F, 0x633A, 0x633A, - 0x633D, 0x633F, 0x6349, 0x6349, 0x634C, 0x634D, 0x634F, 0x6350, 0x6355, 0x6355, 0x6357, 0x6357, 0x635C, 0x635C, 0x6367, 0x6369, - 0x636B, 0x636B, 0x636E, 0x636E, 0x6372, 0x6372, 0x6376, 0x6377, 0x637A, 0x637B, 0x6380, 0x6380, 0x6383, 0x6383, 0x6388, 0x6389, - 0x638C, 0x638C, 0x638E, 0x638F, 0x6392, 0x6392, 0x6396, 0x6396, 0x6398, 0x6398, 0x639B, 0x639B, 0x639F, 0x63A3, 0x63A5, 0x63A5, - 0x63A7, 0x63AC, 0x63B2, 0x63B2, 0x63B4, 0x63B5, 0x63BB, 0x63BB, 0x63BE, 0x63BE, 0x63C0, 0x63C0, 0x63C3, 0x63C4, 0x63C6, 0x63C6, - 0x63C9, 0x63C9, 0x63CF, 0x63D0, 0x63D2, 0x63D2, 0x63D6, 0x63D6, 0x63DA, 0x63DB, 0x63E1, 0x63E1, 0x63E3, 0x63E3, 0x63E9, 0x63E9, - 0x63EE, 0x63EE, 0x63F4, 0x63F4, 0x63F6, 0x63F6, 0x63FA, 0x63FA, 0x6406, 0x6406, 0x640D, 0x640D, 0x640F, 0x640F, 0x6413, 0x6413, - 0x6416, 0x6417, 0x641C, 0x641C, 0x6426, 0x6426, 0x6428, 0x6428, 0x642C, 0x642D, 0x6434, 0x6434, 0x6436, 0x6436, 0x643A, 0x643A, - 0x643E, 0x643E, 0x6442, 0x6442, 0x644E, 0x644E, 0x6458, 0x6458, 0x6467, 0x6467, 0x6469, 0x6469, 0x646F, 0x646F, 0x6476, 0x6476, - 0x6478, 0x6478, 0x647A, 0x647A, 0x6483, 0x6483, 0x6488, 0x6488, 0x6492, 0x6493, 0x6495, 0x6495, 0x649A, 0x649A, 0x649E, 0x649E, - 0x64A4, 0x64A5, 0x64A9, 0x64A9, 0x64AB, 0x64AB, 0x64AD, 0x64AE, 0x64B0, 0x64B0, 0x64B2, 0x64B2, 0x64B9, 0x64B9, 0x64BB, 0x64BC, - 0x64C1, 0x64C2, 0x64C5, 0x64C5, 0x64C7, 0x64C7, 0x64CD, 0x64CD, 0x64D2, 0x64D2, 0x64D4, 0x64D4, 0x64D8, 0x64D8, 0x64DA, 0x64DA, - 0x64E0, 0x64E3, 0x64E6, 0x64E7, 0x64EC, 0x64EC, 0x64EF, 0x64EF, 0x64F1, 0x64F2, 0x64F4, 0x64F4, 0x64F6, 0x64F6, 0x64FA, 0x64FA, - 0x64FD, 0x64FE, 0x6500, 0x6500, 0x6505, 0x6505, 0x6518, 0x6518, 0x651C, 0x651D, 0x6523, 0x6524, 0x652A, 0x652C, 0x652F, 0x652F, - 0x6534, 0x6539, 0x653B, 0x653B, 0x653E, 0x653F, 0x6545, 0x6545, 0x6548, 0x6548, 0x654D, 0x654D, 0x654F, 0x654F, 0x6551, 0x6551, - 0x6555, 0x6559, 0x655D, 0x655E, 0x6562, 0x6563, 0x6566, 0x6566, 0x656C, 0x656C, 0x6570, 0x6570, 0x6572, 0x6572, 0x6574, 0x6575, - 0x6577, 0x6578, 0x6582, 0x6583, 0x6587, 0x6589, 0x658C, 0x658C, 0x658E, 0x658E, 0x6590, 0x6591, 0x6597, 0x6597, 0x6599, 0x6599, - 0x659B, 0x659C, 0x659F, 0x659F, 0x65A1, 0x65A1, 0x65A4, 0x65A5, 0x65A7, 0x65A7, 0x65AB, 0x65AD, 0x65AF, 0x65B0, 0x65B7, 0x65B7, - 0x65B9, 0x65B9, 0x65BC, 0x65BD, 0x65C1, 0x65C1, 0x65C3, 0x65C6, 0x65CB, 0x65CC, 0x65CF, 0x65CF, 0x65D2, 0x65D2, 0x65D7, 0x65D7, - 0x65D9, 0x65D9, 0x65DB, 0x65DB, 0x65E0, 0x65E2, 0x65E5, 0x65E9, 0x65EC, 0x65ED, 0x65F1, 0x65F1, 0x65FA, 0x65FB, 0x6602, 0x6603, - 0x6606, 0x6607, 0x660A, 0x660A, 0x660C, 0x660C, 0x660E, 0x660F, 0x6613, 0x6614, 0x661C, 0x661C, 0x661F, 0x6620, 0x6625, 0x6625, - 0x6627, 0x6628, 0x662D, 0x662D, 0x662F, 0x662F, 0x6634, 0x6636, 0x663C, 0x663C, 0x663F, 0x663F, 0x6641, 0x6644, 0x6649, 0x6649, - 0x664B, 0x664B, 0x664F, 0x664F, 0x6652, 0x6652, 0x665D, 0x665F, 0x6662, 0x6662, 0x6664, 0x6664, 0x6666, 0x6669, 0x666E, 0x6670, - 0x6674, 0x6674, 0x6676, 0x6676, 0x667A, 0x667A, 0x6681, 0x6681, 0x6683, 0x6684, 0x6687, 0x6689, 0x668E, 0x668E, 0x6691, 0x6691, - 0x6696, 0x6698, 0x669D, 0x669D, 0x66A2, 0x66A2, 0x66A6, 0x66A6, 0x66AB, 0x66AB, 0x66AE, 0x66AE, 0x66B4, 0x66B4, 0x66B8, 0x66B9, - 0x66BC, 0x66BC, 0x66BE, 0x66BE, 0x66C1, 0x66C1, 0x66C4, 0x66C4, 0x66C7, 0x66C7, 0x66C9, 0x66C9, 0x66D6, 0x66D6, 0x66D9, 0x66DA, - 0x66DC, 0x66DD, 0x66E0, 0x66E0, 0x66E6, 0x66E6, 0x66E9, 0x66E9, 0x66F0, 0x66F0, 0x66F2, 0x66F5, 0x66F7, 0x66F9, 0x66FC, 0x6700, - 0x6703, 0x6703, 0x6708, 0x6709, 0x670B, 0x670B, 0x670D, 0x670D, 0x670F, 0x670F, 0x6714, 0x6717, 0x671B, 0x671B, 0x671D, 0x671F, - 0x6726, 0x6728, 0x672A, 0x672E, 0x6731, 0x6731, 0x6734, 0x6734, 0x6736, 0x6738, 0x673A, 0x673A, 0x673D, 0x673D, 0x673F, 0x673F, - 0x6741, 0x6741, 0x6746, 0x6746, 0x6749, 0x6749, 0x674E, 0x6751, 0x6753, 0x6753, 0x6756, 0x6756, 0x6759, 0x6759, 0x675C, 0x675C, - 0x675E, 0x6765, 0x676A, 0x676A, 0x676D, 0x676D, 0x676F, 0x6773, 0x6775, 0x6775, 0x6777, 0x6777, 0x677C, 0x677C, 0x677E, 0x677F, - 0x6785, 0x6785, 0x6787, 0x6787, 0x6789, 0x6789, 0x678B, 0x678C, 0x6790, 0x6790, 0x6795, 0x6795, 0x6797, 0x6797, 0x679A, 0x679A, - 0x679C, 0x679D, 0x67A0, 0x67A2, 0x67A6, 0x67A6, 0x67A9, 0x67A9, 0x67AF, 0x67AF, 0x67B3, 0x67B4, 0x67B6, 0x67B9, 0x67C1, 0x67C1, - 0x67C4, 0x67C4, 0x67C6, 0x67C6, 0x67CA, 0x67CA, 0x67CE, 0x67D1, 0x67D3, 0x67D4, 0x67D8, 0x67D8, 0x67DA, 0x67DA, 0x67DD, 0x67DE, - 0x67E2, 0x67E2, 0x67E4, 0x67E4, 0x67E7, 0x67E7, 0x67E9, 0x67E9, 0x67EC, 0x67EC, 0x67EE, 0x67EF, 0x67F1, 0x67F1, 0x67F3, 0x67F5, - 0x67FB, 0x67FB, 0x67FE, 0x67FF, 0x6802, 0x6804, 0x6813, 0x6813, 0x6816, 0x6817, 0x681E, 0x681E, 0x6821, 0x6822, 0x6829, 0x682B, - 0x6832, 0x6832, 0x6834, 0x6834, 0x6838, 0x6839, 0x683C, 0x683D, 0x6840, 0x6843, 0x6846, 0x6846, 0x6848, 0x6848, 0x684D, 0x684E, - 0x6850, 0x6851, 0x6853, 0x6854, 0x6859, 0x6859, 0x685C, 0x685D, 0x685F, 0x685F, 0x6863, 0x6863, 0x6867, 0x6867, 0x6874, 0x6874, - 0x6876, 0x6877, 0x687E, 0x687F, 0x6881, 0x6881, 0x6883, 0x6883, 0x6885, 0x6885, 0x688D, 0x688D, 0x688F, 0x688F, 0x6893, 0x6894, - 0x6897, 0x6897, 0x689B, 0x689B, 0x689D, 0x689D, 0x689F, 0x68A0, 0x68A2, 0x68A2, 0x68A6, 0x68A8, 0x68AD, 0x68AD, 0x68AF, 0x68B1, - 0x68B3, 0x68B3, 0x68B5, 0x68B6, 0x68B9, 0x68BA, 0x68BC, 0x68BC, 0x68C4, 0x68C4, 0x68C6, 0x68C6, 0x68C9, 0x68CB, 0x68CD, 0x68CD, - 0x68D2, 0x68D2, 0x68D4, 0x68D5, 0x68D7, 0x68D8, 0x68DA, 0x68DA, 0x68DF, 0x68E1, 0x68E3, 0x68E3, 0x68E7, 0x68E7, 0x68EE, 0x68EF, - 0x68F2, 0x68F2, 0x68F9, 0x68FA, 0x6900, 0x6901, 0x6904, 0x6905, 0x6908, 0x6908, 0x690B, 0x690F, 0x6912, 0x6912, 0x6919, 0x691C, - 0x6921, 0x6923, 0x6925, 0x6926, 0x6928, 0x6928, 0x692A, 0x692A, 0x6930, 0x6930, 0x6934, 0x6934, 0x6936, 0x6936, 0x6939, 0x6939, - 0x693D, 0x693D, 0x693F, 0x693F, 0x694A, 0x694A, 0x6953, 0x6955, 0x6959, 0x695A, 0x695C, 0x695E, 0x6960, 0x6962, 0x696A, 0x696B, - 0x696D, 0x696F, 0x6973, 0x6975, 0x6977, 0x6979, 0x697C, 0x697E, 0x6981, 0x6982, 0x698A, 0x698A, 0x698E, 0x698E, 0x6991, 0x6991, - 0x6994, 0x6995, 0x699B, 0x699C, 0x69A0, 0x69A0, 0x69A7, 0x69A7, 0x69AE, 0x69AE, 0x69B1, 0x69B2, 0x69B4, 0x69B4, 0x69BB, 0x69BB, - 0x69BE, 0x69BF, 0x69C1, 0x69C1, 0x69C3, 0x69C3, 0x69C7, 0x69C7, 0x69CA, 0x69CE, 0x69D0, 0x69D0, 0x69D3, 0x69D3, 0x69D8, 0x69D9, - 0x69DD, 0x69DE, 0x69E7, 0x69E8, 0x69EB, 0x69EB, 0x69ED, 0x69ED, 0x69F2, 0x69F2, 0x69F9, 0x69F9, 0x69FB, 0x69FB, 0x69FD, 0x69FD, - 0x69FF, 0x69FF, 0x6A02, 0x6A02, 0x6A05, 0x6A05, 0x6A0A, 0x6A0C, 0x6A12, 0x6A14, 0x6A17, 0x6A17, 0x6A19, 0x6A19, 0x6A1B, 0x6A1B, - 0x6A1E, 0x6A1F, 0x6A21, 0x6A23, 0x6A29, 0x6A2B, 0x6A2E, 0x6A2E, 0x6A35, 0x6A36, 0x6A38, 0x6A3A, 0x6A3D, 0x6A3D, 0x6A44, 0x6A44, - 0x6A47, 0x6A48, 0x6A4B, 0x6A4B, 0x6A58, 0x6A59, 0x6A5F, 0x6A5F, 0x6A61, 0x6A62, 0x6A66, 0x6A66, 0x6A72, 0x6A72, 0x6A78, 0x6A78, - 0x6A7F, 0x6A80, 0x6A84, 0x6A84, 0x6A8D, 0x6A8E, 0x6A90, 0x6A90, 0x6A97, 0x6A97, 0x6A9C, 0x6A9C, 0x6AA0, 0x6AA0, 0x6AA2, 0x6AA3, - 0x6AAA, 0x6AAA, 0x6AAC, 0x6AAC, 0x6AAE, 0x6AAE, 0x6AB3, 0x6AB3, 0x6AB8, 0x6AB8, 0x6ABB, 0x6ABB, 0x6AC1, 0x6AC3, 0x6AD1, 0x6AD1, - 0x6AD3, 0x6AD3, 0x6ADA, 0x6ADB, 0x6ADE, 0x6ADF, 0x6AE8, 0x6AE8, 0x6AEA, 0x6AEA, 0x6AFA, 0x6AFB, 0x6B04, 0x6B05, 0x6B0A, 0x6B0A, - 0x6B12, 0x6B12, 0x6B16, 0x6B16, 0x6B1D, 0x6B1D, 0x6B1F, 0x6B21, 0x6B23, 0x6B23, 0x6B27, 0x6B27, 0x6B32, 0x6B32, 0x6B37, 0x6B3A, - 0x6B3D, 0x6B3E, 0x6B43, 0x6B43, 0x6B47, 0x6B47, 0x6B49, 0x6B49, 0x6B4C, 0x6B4C, 0x6B4E, 0x6B4E, 0x6B50, 0x6B50, 0x6B53, 0x6B54, - 0x6B59, 0x6B59, 0x6B5B, 0x6B5B, 0x6B5F, 0x6B5F, 0x6B61, 0x6B64, 0x6B66, 0x6B66, 0x6B69, 0x6B6A, 0x6B6F, 0x6B6F, 0x6B73, 0x6B74, - 0x6B78, 0x6B79, 0x6B7B, 0x6B7B, 0x6B7F, 0x6B80, 0x6B83, 0x6B84, 0x6B86, 0x6B86, 0x6B89, 0x6B8B, 0x6B8D, 0x6B8D, 0x6B95, 0x6B96, - 0x6B98, 0x6B98, 0x6B9E, 0x6B9E, 0x6BA4, 0x6BA4, 0x6BAA, 0x6BAB, 0x6BAF, 0x6BAF, 0x6BB1, 0x6BB5, 0x6BB7, 0x6BB7, 0x6BBA, 0x6BBC, - 0x6BBF, 0x6BC0, 0x6BC5, 0x6BC6, 0x6BCB, 0x6BCB, 0x6BCD, 0x6BCE, 0x6BD2, 0x6BD4, 0x6BD8, 0x6BD8, 0x6BDB, 0x6BDB, 0x6BDF, 0x6BDF, - 0x6BEB, 0x6BEC, 0x6BEF, 0x6BEF, 0x6BF3, 0x6BF3, 0x6C08, 0x6C08, 0x6C0F, 0x6C0F, 0x6C11, 0x6C11, 0x6C13, 0x6C14, 0x6C17, 0x6C17, - 0x6C1B, 0x6C1B, 0x6C23, 0x6C24, 0x6C34, 0x6C34, 0x6C37, 0x6C38, 0x6C3E, 0x6C3E, 0x6C40, 0x6C42, 0x6C4E, 0x6C4E, 0x6C50, 0x6C50, - 0x6C55, 0x6C55, 0x6C57, 0x6C57, 0x6C5A, 0x6C5A, 0x6C5D, 0x6C60, 0x6C62, 0x6C62, 0x6C68, 0x6C68, 0x6C6A, 0x6C6A, 0x6C70, 0x6C70, - 0x6C72, 0x6C73, 0x6C7A, 0x6C7A, 0x6C7D, 0x6C7E, 0x6C81, 0x6C83, 0x6C88, 0x6C88, 0x6C8C, 0x6C8D, 0x6C90, 0x6C90, 0x6C92, 0x6C93, - 0x6C96, 0x6C96, 0x6C99, 0x6C9B, 0x6CA1, 0x6CA2, 0x6CAB, 0x6CAB, 0x6CAE, 0x6CAE, 0x6CB1, 0x6CB1, 0x6CB3, 0x6CB3, 0x6CB8, 0x6CBF, - 0x6CC1, 0x6CC1, 0x6CC4, 0x6CC5, 0x6CC9, 0x6CCA, 0x6CCC, 0x6CCC, 0x6CD3, 0x6CD3, 0x6CD5, 0x6CD5, 0x6CD7, 0x6CD7, 0x6CD9, 0x6CD9, - 0x6CDB, 0x6CDB, 0x6CDD, 0x6CDD, 0x6CE1, 0x6CE3, 0x6CE5, 0x6CE5, 0x6CE8, 0x6CE8, 0x6CEA, 0x6CEA, 0x6CEF, 0x6CF1, 0x6CF3, 0x6CF3, - 0x6D0B, 0x6D0C, 0x6D12, 0x6D12, 0x6D17, 0x6D17, 0x6D19, 0x6D19, 0x6D1B, 0x6D1B, 0x6D1E, 0x6D1F, 0x6D25, 0x6D25, 0x6D29, 0x6D2B, - 0x6D32, 0x6D33, 0x6D35, 0x6D36, 0x6D38, 0x6D38, 0x6D3B, 0x6D3B, 0x6D3D, 0x6D3E, 0x6D41, 0x6D41, 0x6D44, 0x6D45, 0x6D59, 0x6D5A, - 0x6D5C, 0x6D5C, 0x6D63, 0x6D64, 0x6D66, 0x6D66, 0x6D69, 0x6D6A, 0x6D6C, 0x6D6C, 0x6D6E, 0x6D6E, 0x6D74, 0x6D74, 0x6D77, 0x6D79, - 0x6D85, 0x6D85, 0x6D88, 0x6D88, 0x6D8C, 0x6D8C, 0x6D8E, 0x6D8E, 0x6D93, 0x6D93, 0x6D95, 0x6D95, 0x6D99, 0x6D99, 0x6D9B, 0x6D9C, - 0x6DAF, 0x6DAF, 0x6DB2, 0x6DB2, 0x6DB5, 0x6DB5, 0x6DB8, 0x6DB8, 0x6DBC, 0x6DBC, 0x6DC0, 0x6DC0, 0x6DC5, 0x6DC7, 0x6DCB, 0x6DCC, - 0x6DD1, 0x6DD2, 0x6DD5, 0x6DD5, 0x6DD8, 0x6DD9, 0x6DDE, 0x6DDE, 0x6DE1, 0x6DE1, 0x6DE4, 0x6DE4, 0x6DE6, 0x6DE6, 0x6DE8, 0x6DE8, - 0x6DEA, 0x6DEC, 0x6DEE, 0x6DEE, 0x6DF1, 0x6DF1, 0x6DF3, 0x6DF3, 0x6DF5, 0x6DF5, 0x6DF7, 0x6DF7, 0x6DF9, 0x6DFB, 0x6E05, 0x6E05, - 0x6E07, 0x6E0B, 0x6E13, 0x6E13, 0x6E15, 0x6E15, 0x6E19, 0x6E1B, 0x6E1D, 0x6E1D, 0x6E1F, 0x6E21, 0x6E23, 0x6E26, 0x6E29, 0x6E29, - 0x6E2B, 0x6E2F, 0x6E38, 0x6E38, 0x6E3A, 0x6E3A, 0x6E3E, 0x6E3E, 0x6E43, 0x6E43, 0x6E4A, 0x6E4A, 0x6E4D, 0x6E4E, 0x6E56, 0x6E56, - 0x6E58, 0x6E58, 0x6E5B, 0x6E5B, 0x6E5F, 0x6E5F, 0x6E67, 0x6E67, 0x6E6B, 0x6E6B, 0x6E6E, 0x6E6F, 0x6E72, 0x6E72, 0x6E76, 0x6E76, - 0x6E7E, 0x6E80, 0x6E82, 0x6E82, 0x6E8C, 0x6E8C, 0x6E8F, 0x6E90, 0x6E96, 0x6E96, 0x6E98, 0x6E98, 0x6E9C, 0x6E9D, 0x6E9F, 0x6E9F, - 0x6EA2, 0x6EA2, 0x6EA5, 0x6EA5, 0x6EAA, 0x6EAA, 0x6EAF, 0x6EAF, 0x6EB2, 0x6EB2, 0x6EB6, 0x6EB7, 0x6EBA, 0x6EBA, 0x6EBD, 0x6EBD, - 0x6EC2, 0x6EC2, 0x6EC4, 0x6EC5, 0x6EC9, 0x6EC9, 0x6ECB, 0x6ECC, 0x6ED1, 0x6ED1, 0x6ED3, 0x6ED5, 0x6EDD, 0x6EDE, 0x6EEC, 0x6EEC, - 0x6EEF, 0x6EEF, 0x6EF2, 0x6EF2, 0x6EF4, 0x6EF4, 0x6EF7, 0x6EF8, 0x6EFE, 0x6EFF, 0x6F01, 0x6F02, 0x6F06, 0x6F06, 0x6F09, 0x6F09, - 0x6F0F, 0x6F0F, 0x6F11, 0x6F11, 0x6F13, 0x6F15, 0x6F20, 0x6F20, 0x6F22, 0x6F23, 0x6F2B, 0x6F2C, 0x6F31, 0x6F32, 0x6F38, 0x6F38, - 0x6F3E, 0x6F3F, 0x6F41, 0x6F41, 0x6F45, 0x6F45, 0x6F54, 0x6F54, 0x6F58, 0x6F58, 0x6F5B, 0x6F5C, 0x6F5F, 0x6F5F, 0x6F64, 0x6F64, - 0x6F66, 0x6F66, 0x6F6D, 0x6F70, 0x6F74, 0x6F74, 0x6F78, 0x6F78, 0x6F7A, 0x6F7A, 0x6F7C, 0x6F7C, 0x6F80, 0x6F82, 0x6F84, 0x6F84, - 0x6F86, 0x6F86, 0x6F8E, 0x6F8E, 0x6F91, 0x6F91, 0x6F97, 0x6F97, 0x6FA1, 0x6FA1, 0x6FA3, 0x6FA4, 0x6FAA, 0x6FAA, 0x6FB1, 0x6FB1, - 0x6FB3, 0x6FB3, 0x6FB9, 0x6FB9, 0x6FC0, 0x6FC3, 0x6FC6, 0x6FC6, 0x6FD4, 0x6FD5, 0x6FD8, 0x6FD8, 0x6FDB, 0x6FDB, 0x6FDF, 0x6FE1, - 0x6FE4, 0x6FE4, 0x6FEB, 0x6FEC, 0x6FEE, 0x6FEF, 0x6FF1, 0x6FF1, 0x6FF3, 0x6FF3, 0x6FF6, 0x6FF6, 0x6FFA, 0x6FFA, 0x6FFE, 0x6FFE, - 0x7001, 0x7001, 0x7009, 0x7009, 0x700B, 0x700B, 0x700F, 0x700F, 0x7011, 0x7011, 0x7015, 0x7015, 0x7018, 0x7018, 0x701A, 0x701B, - 0x701D, 0x701F, 0x7026, 0x7027, 0x702C, 0x702C, 0x7030, 0x7030, 0x7032, 0x7032, 0x703E, 0x703E, 0x704C, 0x704C, 0x7051, 0x7051, - 0x7058, 0x7058, 0x7063, 0x7063, 0x706B, 0x706B, 0x706F, 0x7070, 0x7078, 0x7078, 0x707C, 0x707D, 0x7089, 0x708A, 0x708E, 0x708E, - 0x7092, 0x7092, 0x7099, 0x7099, 0x70AC, 0x70AF, 0x70B3, 0x70B3, 0x70B8, 0x70BA, 0x70C8, 0x70C8, 0x70CB, 0x70CB, 0x70CF, 0x70CF, - 0x70D9, 0x70D9, 0x70DD, 0x70DD, 0x70DF, 0x70DF, 0x70F1, 0x70F1, 0x70F9, 0x70F9, 0x70FD, 0x70FD, 0x7109, 0x7109, 0x7114, 0x7114, - 0x7119, 0x711A, 0x711C, 0x711C, 0x7121, 0x7121, 0x7126, 0x7126, 0x7136, 0x7136, 0x713C, 0x713C, 0x7149, 0x7149, 0x714C, 0x714C, - 0x714E, 0x714E, 0x7155, 0x7156, 0x7159, 0x7159, 0x7162, 0x7162, 0x7164, 0x7167, 0x7169, 0x7169, 0x716C, 0x716C, 0x716E, 0x716E, - 0x717D, 0x717D, 0x7184, 0x7184, 0x7188, 0x7188, 0x718A, 0x718A, 0x718F, 0x718F, 0x7194, 0x7195, 0x7199, 0x7199, 0x719F, 0x719F, - 0x71A8, 0x71A8, 0x71AC, 0x71AC, 0x71B1, 0x71B1, 0x71B9, 0x71B9, 0x71BE, 0x71BE, 0x71C3, 0x71C3, 0x71C8, 0x71C9, 0x71CE, 0x71CE, - 0x71D0, 0x71D0, 0x71D2, 0x71D2, 0x71D4, 0x71D5, 0x71D7, 0x71D7, 0x71DF, 0x71E0, 0x71E5, 0x71E7, 0x71EC, 0x71EE, 0x71F5, 0x71F5, - 0x71F9, 0x71F9, 0x71FB, 0x71FC, 0x71FF, 0x71FF, 0x7206, 0x7206, 0x720D, 0x720D, 0x7210, 0x7210, 0x721B, 0x721B, 0x7228, 0x7228, - 0x722A, 0x722A, 0x722C, 0x722D, 0x7230, 0x7230, 0x7232, 0x7232, 0x7235, 0x7236, 0x723A, 0x7240, 0x7246, 0x7248, 0x724B, 0x724C, - 0x7252, 0x7252, 0x7258, 0x7259, 0x725B, 0x725B, 0x725D, 0x725D, 0x725F, 0x725F, 0x7261, 0x7262, 0x7267, 0x7267, 0x7269, 0x7269, - 0x7272, 0x7272, 0x7274, 0x7274, 0x7279, 0x7279, 0x727D, 0x727E, 0x7280, 0x7282, 0x7287, 0x7287, 0x7292, 0x7292, 0x7296, 0x7296, - 0x72A0, 0x72A0, 0x72A2, 0x72A2, 0x72A7, 0x72A7, 0x72AC, 0x72AC, 0x72AF, 0x72AF, 0x72B2, 0x72B2, 0x72B6, 0x72B6, 0x72B9, 0x72B9, - 0x72C2, 0x72C4, 0x72C6, 0x72C6, 0x72CE, 0x72CE, 0x72D0, 0x72D0, 0x72D2, 0x72D2, 0x72D7, 0x72D7, 0x72D9, 0x72D9, 0x72DB, 0x72DB, - 0x72E0, 0x72E2, 0x72E9, 0x72E9, 0x72EC, 0x72ED, 0x72F7, 0x72F9, 0x72FC, 0x72FD, 0x730A, 0x730A, 0x7316, 0x7317, 0x731B, 0x731D, - 0x731F, 0x731F, 0x7325, 0x7325, 0x7329, 0x732B, 0x732E, 0x732F, 0x7334, 0x7334, 0x7336, 0x7337, 0x733E, 0x733F, 0x7344, 0x7345, - 0x734E, 0x734F, 0x7357, 0x7357, 0x7363, 0x7363, 0x7368, 0x7368, 0x736A, 0x736A, 0x7370, 0x7370, 0x7372, 0x7372, 0x7375, 0x7375, - 0x7378, 0x7378, 0x737A, 0x737B, 0x7384, 0x7384, 0x7387, 0x7387, 0x7389, 0x7389, 0x738B, 0x738B, 0x7396, 0x7396, 0x73A9, 0x73A9, - 0x73B2, 0x73B3, 0x73BB, 0x73BB, 0x73C0, 0x73C0, 0x73C2, 0x73C2, 0x73C8, 0x73C8, 0x73CA, 0x73CA, 0x73CD, 0x73CE, 0x73DE, 0x73DE, - 0x73E0, 0x73E0, 0x73E5, 0x73E5, 0x73EA, 0x73EA, 0x73ED, 0x73EE, 0x73F1, 0x73F1, 0x73F8, 0x73F8, 0x73FE, 0x73FE, 0x7403, 0x7403, - 0x7405, 0x7406, 0x7409, 0x7409, 0x7422, 0x7422, 0x7425, 0x7425, 0x7432, 0x7436, 0x743A, 0x743A, 0x743F, 0x743F, 0x7441, 0x7441, - 0x7455, 0x7455, 0x7459, 0x745C, 0x745E, 0x7460, 0x7463, 0x7464, 0x7469, 0x746A, 0x746F, 0x7470, 0x7473, 0x7473, 0x7476, 0x7476, - 0x747E, 0x747E, 0x7483, 0x7483, 0x748B, 0x748B, 0x749E, 0x749E, 0x74A2, 0x74A2, 0x74A7, 0x74A7, 0x74B0, 0x74B0, 0x74BD, 0x74BD, - 0x74CA, 0x74CA, 0x74CF, 0x74CF, 0x74D4, 0x74D4, 0x74DC, 0x74DC, 0x74E0, 0x74E0, 0x74E2, 0x74E3, 0x74E6, 0x74E7, 0x74E9, 0x74E9, - 0x74EE, 0x74EE, 0x74F0, 0x74F2, 0x74F6, 0x74F8, 0x7503, 0x7505, 0x750C, 0x750E, 0x7511, 0x7511, 0x7513, 0x7513, 0x7515, 0x7515, - 0x7518, 0x7518, 0x751A, 0x751A, 0x751C, 0x751C, 0x751E, 0x751F, 0x7523, 0x7523, 0x7525, 0x7526, 0x7528, 0x7528, 0x752B, 0x752C, - 0x7530, 0x7533, 0x7537, 0x7538, 0x753A, 0x753C, 0x7544, 0x7544, 0x7546, 0x7546, 0x7549, 0x754D, 0x754F, 0x754F, 0x7551, 0x7551, - 0x7554, 0x7554, 0x7559, 0x755D, 0x7560, 0x7560, 0x7562, 0x7562, 0x7564, 0x7567, 0x7569, 0x756B, 0x756D, 0x756D, 0x7570, 0x7570, - 0x7573, 0x7574, 0x7576, 0x7578, 0x757F, 0x757F, 0x7582, 0x7582, 0x7586, 0x7587, 0x7589, 0x758B, 0x758E, 0x758F, 0x7591, 0x7591, - 0x7594, 0x7594, 0x759A, 0x759A, 0x759D, 0x759D, 0x75A3, 0x75A3, 0x75A5, 0x75A5, 0x75AB, 0x75AB, 0x75B1, 0x75B3, 0x75B5, 0x75B5, - 0x75B8, 0x75B9, 0x75BC, 0x75BE, 0x75C2, 0x75C3, 0x75C5, 0x75C5, 0x75C7, 0x75C7, 0x75CA, 0x75CA, 0x75CD, 0x75CD, 0x75D2, 0x75D2, - 0x75D4, 0x75D5, 0x75D8, 0x75D9, 0x75DB, 0x75DB, 0x75DE, 0x75DE, 0x75E2, 0x75E3, 0x75E9, 0x75E9, 0x75F0, 0x75F0, 0x75F2, 0x75F4, - 0x75FA, 0x75FA, 0x75FC, 0x75FC, 0x75FE, 0x75FF, 0x7601, 0x7601, 0x7609, 0x7609, 0x760B, 0x760B, 0x760D, 0x760D, 0x761F, 0x7622, - 0x7624, 0x7624, 0x7627, 0x7627, 0x7630, 0x7630, 0x7634, 0x7634, 0x763B, 0x763B, 0x7642, 0x7642, 0x7646, 0x7648, 0x764C, 0x764C, - 0x7652, 0x7652, 0x7656, 0x7656, 0x7658, 0x7658, 0x765C, 0x765C, 0x7661, 0x7662, 0x7667, 0x766A, 0x766C, 0x766C, 0x7670, 0x7670, - 0x7672, 0x7672, 0x7676, 0x7676, 0x7678, 0x7678, 0x767A, 0x767E, 0x7680, 0x7680, 0x7683, 0x7684, 0x7686, 0x7688, 0x768B, 0x768B, - 0x768E, 0x768E, 0x7690, 0x7690, 0x7693, 0x7693, 0x7696, 0x7696, 0x7699, 0x769A, 0x76AE, 0x76AE, 0x76B0, 0x76B0, 0x76B4, 0x76B4, - 0x76B7, 0x76BA, 0x76BF, 0x76BF, 0x76C2, 0x76C3, 0x76C6, 0x76C6, 0x76C8, 0x76C8, 0x76CA, 0x76CA, 0x76CD, 0x76CD, 0x76D2, 0x76D2, - 0x76D6, 0x76D7, 0x76DB, 0x76DC, 0x76DE, 0x76DF, 0x76E1, 0x76E1, 0x76E3, 0x76E5, 0x76E7, 0x76E7, 0x76EA, 0x76EA, 0x76EE, 0x76EE, - 0x76F2, 0x76F2, 0x76F4, 0x76F4, 0x76F8, 0x76F8, 0x76FB, 0x76FB, 0x76FE, 0x76FE, 0x7701, 0x7701, 0x7704, 0x7704, 0x7707, 0x7709, - 0x770B, 0x770C, 0x771B, 0x771B, 0x771E, 0x7720, 0x7724, 0x7726, 0x7729, 0x7729, 0x7737, 0x7738, 0x773A, 0x773A, 0x773C, 0x773C, - 0x7740, 0x7740, 0x7747, 0x7747, 0x775A, 0x775B, 0x7761, 0x7761, 0x7763, 0x7763, 0x7765, 0x7766, 0x7768, 0x7768, 0x776B, 0x776B, - 0x7779, 0x7779, 0x777E, 0x777F, 0x778B, 0x778B, 0x778E, 0x778E, 0x7791, 0x7791, 0x779E, 0x779E, 0x77A0, 0x77A0, 0x77A5, 0x77A5, - 0x77AC, 0x77AD, 0x77B0, 0x77B0, 0x77B3, 0x77B3, 0x77B6, 0x77B6, 0x77B9, 0x77B9, 0x77BB, 0x77BD, 0x77BF, 0x77BF, 0x77C7, 0x77C7, - 0x77CD, 0x77CD, 0x77D7, 0x77D7, 0x77DA, 0x77DC, 0x77E2, 0x77E3, 0x77E5, 0x77E5, 0x77E7, 0x77E7, 0x77E9, 0x77E9, 0x77ED, 0x77EF, - 0x77F3, 0x77F3, 0x77FC, 0x77FC, 0x7802, 0x7802, 0x780C, 0x780C, 0x7812, 0x7812, 0x7814, 0x7815, 0x7820, 0x7820, 0x7825, 0x7827, - 0x7832, 0x7832, 0x7834, 0x7834, 0x783A, 0x783A, 0x783F, 0x783F, 0x7845, 0x7845, 0x785D, 0x785D, 0x786B, 0x786C, 0x786F, 0x786F, - 0x7872, 0x7872, 0x7874, 0x7874, 0x787C, 0x787C, 0x7881, 0x7881, 0x7886, 0x7887, 0x788C, 0x788E, 0x7891, 0x7891, 0x7893, 0x7893, - 0x7895, 0x7895, 0x7897, 0x7897, 0x789A, 0x789A, 0x78A3, 0x78A3, 0x78A7, 0x78A7, 0x78A9, 0x78AA, 0x78AF, 0x78AF, 0x78B5, 0x78B5, - 0x78BA, 0x78BA, 0x78BC, 0x78BC, 0x78BE, 0x78BE, 0x78C1, 0x78C1, 0x78C5, 0x78C6, 0x78CA, 0x78CB, 0x78D0, 0x78D1, 0x78D4, 0x78D4, - 0x78DA, 0x78DA, 0x78E7, 0x78E8, 0x78EC, 0x78EC, 0x78EF, 0x78EF, 0x78F4, 0x78F4, 0x78FD, 0x78FD, 0x7901, 0x7901, 0x7907, 0x7907, - 0x790E, 0x790E, 0x7911, 0x7912, 0x7919, 0x7919, 0x7926, 0x7926, 0x792A, 0x792C, 0x793A, 0x793A, 0x793C, 0x793C, 0x793E, 0x793E, - 0x7940, 0x7941, 0x7947, 0x7949, 0x7950, 0x7950, 0x7953, 0x7953, 0x7955, 0x7957, 0x795A, 0x795A, 0x795D, 0x7960, 0x7962, 0x7962, - 0x7965, 0x7965, 0x7968, 0x7968, 0x796D, 0x796D, 0x7977, 0x7977, 0x797A, 0x797A, 0x797F, 0x7981, 0x7984, 0x7985, 0x798A, 0x798A, - 0x798D, 0x798F, 0x799D, 0x799D, 0x79A6, 0x79A7, 0x79AA, 0x79AA, 0x79AE, 0x79AE, 0x79B0, 0x79B0, 0x79B3, 0x79B3, 0x79B9, 0x79BA, - 0x79BD, 0x79C1, 0x79C9, 0x79C9, 0x79CB, 0x79CB, 0x79D1, 0x79D2, 0x79D5, 0x79D5, 0x79D8, 0x79D8, 0x79DF, 0x79DF, 0x79E1, 0x79E1, - 0x79E3, 0x79E4, 0x79E6, 0x79E7, 0x79E9, 0x79E9, 0x79EC, 0x79EC, 0x79F0, 0x79F0, 0x79FB, 0x79FB, 0x7A00, 0x7A00, 0x7A08, 0x7A08, - 0x7A0B, 0x7A0B, 0x7A0D, 0x7A0E, 0x7A14, 0x7A14, 0x7A17, 0x7A1A, 0x7A1C, 0x7A1C, 0x7A1F, 0x7A20, 0x7A2E, 0x7A2E, 0x7A31, 0x7A32, - 0x7A37, 0x7A37, 0x7A3B, 0x7A40, 0x7A42, 0x7A43, 0x7A46, 0x7A46, 0x7A49, 0x7A49, 0x7A4D, 0x7A50, 0x7A57, 0x7A57, 0x7A61, 0x7A63, - 0x7A69, 0x7A69, 0x7A6B, 0x7A6B, 0x7A70, 0x7A70, 0x7A74, 0x7A74, 0x7A76, 0x7A76, 0x7A79, 0x7A7A, 0x7A7D, 0x7A7D, 0x7A7F, 0x7A7F, - 0x7A81, 0x7A81, 0x7A83, 0x7A84, 0x7A88, 0x7A88, 0x7A92, 0x7A93, 0x7A95, 0x7A98, 0x7A9F, 0x7A9F, 0x7AA9, 0x7AAA, 0x7AAE, 0x7AB0, - 0x7AB6, 0x7AB6, 0x7ABA, 0x7ABA, 0x7ABF, 0x7ABF, 0x7AC3, 0x7AC5, 0x7AC7, 0x7AC8, 0x7ACA, 0x7ACB, 0x7ACD, 0x7ACD, 0x7ACF, 0x7ACF, - 0x7AD2, 0x7AD3, 0x7AD5, 0x7AD5, 0x7AD9, 0x7ADA, 0x7ADC, 0x7ADD, 0x7ADF, 0x7AE3, 0x7AE5, 0x7AE6, 0x7AEA, 0x7AEA, 0x7AED, 0x7AED, - 0x7AEF, 0x7AF0, 0x7AF6, 0x7AF6, 0x7AF8, 0x7AFA, 0x7AFF, 0x7AFF, 0x7B02, 0x7B02, 0x7B04, 0x7B04, 0x7B06, 0x7B06, 0x7B08, 0x7B08, - 0x7B0A, 0x7B0B, 0x7B0F, 0x7B0F, 0x7B11, 0x7B11, 0x7B18, 0x7B19, 0x7B1B, 0x7B1B, 0x7B1E, 0x7B1E, 0x7B20, 0x7B20, 0x7B25, 0x7B26, - 0x7B28, 0x7B28, 0x7B2C, 0x7B2C, 0x7B33, 0x7B33, 0x7B35, 0x7B36, 0x7B39, 0x7B39, 0x7B45, 0x7B46, 0x7B48, 0x7B49, 0x7B4B, 0x7B4D, - 0x7B4F, 0x7B52, 0x7B54, 0x7B54, 0x7B56, 0x7B56, 0x7B5D, 0x7B5D, 0x7B65, 0x7B65, 0x7B67, 0x7B67, 0x7B6C, 0x7B6C, 0x7B6E, 0x7B6E, - 0x7B70, 0x7B71, 0x7B74, 0x7B75, 0x7B7A, 0x7B7A, 0x7B86, 0x7B87, 0x7B8B, 0x7B8B, 0x7B8D, 0x7B8D, 0x7B8F, 0x7B8F, 0x7B92, 0x7B92, - 0x7B94, 0x7B95, 0x7B97, 0x7B9A, 0x7B9C, 0x7B9D, 0x7B9F, 0x7B9F, 0x7BA1, 0x7BA1, 0x7BAA, 0x7BAA, 0x7BAD, 0x7BAD, 0x7BB1, 0x7BB1, - 0x7BB4, 0x7BB4, 0x7BB8, 0x7BB8, 0x7BC0, 0x7BC1, 0x7BC4, 0x7BC4, 0x7BC6, 0x7BC7, 0x7BC9, 0x7BC9, 0x7BCB, 0x7BCC, 0x7BCF, 0x7BCF, - 0x7BDD, 0x7BDD, 0x7BE0, 0x7BE0, 0x7BE4, 0x7BE6, 0x7BE9, 0x7BE9, 0x7BED, 0x7BED, 0x7BF3, 0x7BF3, 0x7BF6, 0x7BF7, 0x7C00, 0x7C00, - 0x7C07, 0x7C07, 0x7C0D, 0x7C0D, 0x7C11, 0x7C14, 0x7C17, 0x7C17, 0x7C1F, 0x7C1F, 0x7C21, 0x7C21, 0x7C23, 0x7C23, 0x7C27, 0x7C27, - 0x7C2A, 0x7C2B, 0x7C37, 0x7C38, 0x7C3D, 0x7C40, 0x7C43, 0x7C43, 0x7C4C, 0x7C4D, 0x7C4F, 0x7C50, 0x7C54, 0x7C54, 0x7C56, 0x7C56, - 0x7C58, 0x7C58, 0x7C5F, 0x7C60, 0x7C64, 0x7C65, 0x7C6C, 0x7C6C, 0x7C73, 0x7C73, 0x7C75, 0x7C75, 0x7C7E, 0x7C7E, 0x7C81, 0x7C83, - 0x7C89, 0x7C89, 0x7C8B, 0x7C8B, 0x7C8D, 0x7C8D, 0x7C90, 0x7C90, 0x7C92, 0x7C92, 0x7C95, 0x7C95, 0x7C97, 0x7C98, 0x7C9B, 0x7C9B, - 0x7C9F, 0x7C9F, 0x7CA1, 0x7CA2, 0x7CA4, 0x7CA5, 0x7CA7, 0x7CA8, 0x7CAB, 0x7CAB, 0x7CAD, 0x7CAE, 0x7CB1, 0x7CB3, 0x7CB9, 0x7CB9, - 0x7CBD, 0x7CBE, 0x7CC0, 0x7CC0, 0x7CC2, 0x7CC2, 0x7CC5, 0x7CC5, 0x7CCA, 0x7CCA, 0x7CCE, 0x7CCE, 0x7CD2, 0x7CD2, 0x7CD6, 0x7CD6, - 0x7CD8, 0x7CD8, 0x7CDC, 0x7CDC, 0x7CDE, 0x7CE0, 0x7CE2, 0x7CE2, 0x7CE7, 0x7CE7, 0x7CEF, 0x7CEF, 0x7CF2, 0x7CF2, 0x7CF4, 0x7CF4, - 0x7CF6, 0x7CF6, 0x7CF8, 0x7CF8, 0x7CFA, 0x7CFB, 0x7CFE, 0x7CFE, 0x7D00, 0x7D00, 0x7D02, 0x7D02, 0x7D04, 0x7D06, 0x7D0A, 0x7D0B, - 0x7D0D, 0x7D0D, 0x7D10, 0x7D10, 0x7D14, 0x7D15, 0x7D17, 0x7D1C, 0x7D20, 0x7D22, 0x7D2B, 0x7D2C, 0x7D2E, 0x7D30, 0x7D32, 0x7D33, - 0x7D35, 0x7D35, 0x7D39, 0x7D3A, 0x7D3F, 0x7D3F, 0x7D42, 0x7D46, 0x7D4B, 0x7D4C, 0x7D4E, 0x7D50, 0x7D56, 0x7D56, 0x7D5B, 0x7D5B, - 0x7D5E, 0x7D5E, 0x7D61, 0x7D63, 0x7D66, 0x7D66, 0x7D68, 0x7D68, 0x7D6E, 0x7D6E, 0x7D71, 0x7D73, 0x7D75, 0x7D76, 0x7D79, 0x7D79, - 0x7D7D, 0x7D7D, 0x7D89, 0x7D89, 0x7D8F, 0x7D8F, 0x7D93, 0x7D93, 0x7D99, 0x7D9C, 0x7D9F, 0x7D9F, 0x7DA2, 0x7DA3, 0x7DAB, 0x7DB2, - 0x7DB4, 0x7DB5, 0x7DB8, 0x7DB8, 0x7DBA, 0x7DBB, 0x7DBD, 0x7DBF, 0x7DC7, 0x7DC7, 0x7DCA, 0x7DCB, 0x7DCF, 0x7DCF, 0x7DD1, 0x7DD2, - 0x7DD5, 0x7DD5, 0x7DD8, 0x7DD8, 0x7DDA, 0x7DDA, 0x7DDC, 0x7DDE, 0x7DE0, 0x7DE1, 0x7DE4, 0x7DE4, 0x7DE8, 0x7DE9, 0x7DEC, 0x7DEC, - 0x7DEF, 0x7DEF, 0x7DF2, 0x7DF2, 0x7DF4, 0x7DF4, 0x7DFB, 0x7DFB, 0x7E01, 0x7E01, 0x7E04, 0x7E05, 0x7E09, 0x7E0B, 0x7E12, 0x7E12, - 0x7E1B, 0x7E1B, 0x7E1E, 0x7E1F, 0x7E21, 0x7E23, 0x7E26, 0x7E26, 0x7E2B, 0x7E2B, 0x7E2E, 0x7E2E, 0x7E31, 0x7E32, 0x7E35, 0x7E35, - 0x7E37, 0x7E37, 0x7E39, 0x7E3B, 0x7E3D, 0x7E3E, 0x7E41, 0x7E41, 0x7E43, 0x7E43, 0x7E46, 0x7E46, 0x7E4A, 0x7E4B, 0x7E4D, 0x7E4D, - 0x7E54, 0x7E56, 0x7E59, 0x7E5A, 0x7E5D, 0x7E5E, 0x7E66, 0x7E67, 0x7E69, 0x7E6A, 0x7E6D, 0x7E6D, 0x7E70, 0x7E70, 0x7E79, 0x7E79, - 0x7E7B, 0x7E7D, 0x7E7F, 0x7E7F, 0x7E82, 0x7E83, 0x7E88, 0x7E89, 0x7E8C, 0x7E8C, 0x7E8E, 0x7E90, 0x7E92, 0x7E94, 0x7E96, 0x7E96, - 0x7E9B, 0x7E9C, 0x7F36, 0x7F36, 0x7F38, 0x7F38, 0x7F3A, 0x7F3A, 0x7F45, 0x7F45, 0x7F4C, 0x7F4E, 0x7F50, 0x7F51, 0x7F54, 0x7F55, - 0x7F58, 0x7F58, 0x7F5F, 0x7F60, 0x7F67, 0x7F6B, 0x7F6E, 0x7F6E, 0x7F70, 0x7F70, 0x7F72, 0x7F72, 0x7F75, 0x7F75, 0x7F77, 0x7F79, - 0x7F82, 0x7F83, 0x7F85, 0x7F88, 0x7F8A, 0x7F8A, 0x7F8C, 0x7F8C, 0x7F8E, 0x7F8E, 0x7F94, 0x7F94, 0x7F9A, 0x7F9A, 0x7F9D, 0x7F9E, - 0x7FA3, 0x7FA4, 0x7FA8, 0x7FA9, 0x7FAE, 0x7FAF, 0x7FB2, 0x7FB2, 0x7FB6, 0x7FB6, 0x7FB8, 0x7FB9, 0x7FBD, 0x7FBD, 0x7FC1, 0x7FC1, - 0x7FC5, 0x7FC6, 0x7FCA, 0x7FCA, 0x7FCC, 0x7FCC, 0x7FD2, 0x7FD2, 0x7FD4, 0x7FD5, 0x7FE0, 0x7FE1, 0x7FE6, 0x7FE6, 0x7FE9, 0x7FE9, - 0x7FEB, 0x7FEB, 0x7FF0, 0x7FF0, 0x7FF3, 0x7FF3, 0x7FF9, 0x7FF9, 0x7FFB, 0x7FFC, 0x8000, 0x8001, 0x8003, 0x8006, 0x800B, 0x800C, - 0x8010, 0x8010, 0x8012, 0x8012, 0x8015, 0x8015, 0x8017, 0x8019, 0x801C, 0x801C, 0x8021, 0x8021, 0x8028, 0x8028, 0x8033, 0x8033, - 0x8036, 0x8036, 0x803B, 0x803B, 0x803D, 0x803D, 0x803F, 0x803F, 0x8046, 0x8046, 0x804A, 0x804A, 0x8052, 0x8052, 0x8056, 0x8056, - 0x8058, 0x8058, 0x805A, 0x805A, 0x805E, 0x805F, 0x8061, 0x8062, 0x8068, 0x8068, 0x806F, 0x8070, 0x8072, 0x8074, 0x8076, 0x8077, - 0x8079, 0x8079, 0x807D, 0x807F, 0x8084, 0x8087, 0x8089, 0x8089, 0x808B, 0x808C, 0x8093, 0x8093, 0x8096, 0x8096, 0x8098, 0x8098, - 0x809A, 0x809B, 0x809D, 0x809D, 0x80A1, 0x80A2, 0x80A5, 0x80A5, 0x80A9, 0x80AA, 0x80AC, 0x80AD, 0x80AF, 0x80AF, 0x80B1, 0x80B2, - 0x80B4, 0x80B4, 0x80BA, 0x80BA, 0x80C3, 0x80C4, 0x80C6, 0x80C6, 0x80CC, 0x80CC, 0x80CE, 0x80CE, 0x80D6, 0x80D6, 0x80D9, 0x80DB, - 0x80DD, 0x80DE, 0x80E1, 0x80E1, 0x80E4, 0x80E5, 0x80EF, 0x80EF, 0x80F1, 0x80F1, 0x80F4, 0x80F4, 0x80F8, 0x80F8, 0x80FC, 0x80FD, - 0x8102, 0x8102, 0x8105, 0x810A, 0x811A, 0x811B, 0x8123, 0x8123, 0x8129, 0x8129, 0x812F, 0x812F, 0x8131, 0x8131, 0x8133, 0x8133, - 0x8139, 0x8139, 0x813E, 0x813E, 0x8146, 0x8146, 0x814B, 0x814B, 0x814E, 0x814E, 0x8150, 0x8151, 0x8153, 0x8155, 0x815F, 0x815F, - 0x8165, 0x8166, 0x816B, 0x816B, 0x816E, 0x816E, 0x8170, 0x8171, 0x8174, 0x8174, 0x8178, 0x817A, 0x817F, 0x8180, 0x8182, 0x8183, - 0x8188, 0x8188, 0x818A, 0x818A, 0x818F, 0x818F, 0x8193, 0x8193, 0x8195, 0x8195, 0x819A, 0x819A, 0x819C, 0x819D, 0x81A0, 0x81A0, - 0x81A3, 0x81A4, 0x81A8, 0x81A9, 0x81B0, 0x81B0, 0x81B3, 0x81B3, 0x81B5, 0x81B5, 0x81B8, 0x81B8, 0x81BA, 0x81BA, 0x81BD, 0x81C0, - 0x81C2, 0x81C2, 0x81C6, 0x81C6, 0x81C8, 0x81C9, 0x81CD, 0x81CD, 0x81D1, 0x81D1, 0x81D3, 0x81D3, 0x81D8, 0x81DA, 0x81DF, 0x81E0, - 0x81E3, 0x81E3, 0x81E5, 0x81E5, 0x81E7, 0x81E8, 0x81EA, 0x81EA, 0x81ED, 0x81ED, 0x81F3, 0x81F4, 0x81FA, 0x81FC, 0x81FE, 0x81FE, - 0x8201, 0x8202, 0x8205, 0x8205, 0x8207, 0x820A, 0x820C, 0x820E, 0x8210, 0x8210, 0x8212, 0x8212, 0x8216, 0x8218, 0x821B, 0x821C, - 0x821E, 0x821F, 0x8229, 0x822C, 0x822E, 0x822E, 0x8233, 0x8233, 0x8235, 0x8239, 0x8240, 0x8240, 0x8247, 0x8247, 0x8258, 0x825A, - 0x825D, 0x825D, 0x825F, 0x825F, 0x8262, 0x8262, 0x8264, 0x8264, 0x8266, 0x8266, 0x8268, 0x8268, 0x826A, 0x826B, 0x826E, 0x826F, - 0x8271, 0x8272, 0x8276, 0x8278, 0x827E, 0x827E, 0x828B, 0x828B, 0x828D, 0x828D, 0x8292, 0x8292, 0x8299, 0x8299, 0x829D, 0x829D, - 0x829F, 0x829F, 0x82A5, 0x82A6, 0x82AB, 0x82AD, 0x82AF, 0x82AF, 0x82B1, 0x82B1, 0x82B3, 0x82B3, 0x82B8, 0x82B9, 0x82BB, 0x82BB, - 0x82BD, 0x82BD, 0x82C5, 0x82C5, 0x82D1, 0x82D4, 0x82D7, 0x82D7, 0x82D9, 0x82D9, 0x82DB, 0x82DC, 0x82DE, 0x82DF, 0x82E1, 0x82E1, - 0x82E3, 0x82E3, 0x82E5, 0x82E7, 0x82EB, 0x82EB, 0x82F1, 0x82F1, 0x82F3, 0x82F4, 0x82F9, 0x82FB, 0x8302, 0x8306, 0x8309, 0x8309, - 0x830E, 0x830E, 0x8316, 0x8318, 0x831C, 0x831C, 0x8323, 0x8323, 0x8328, 0x8328, 0x832B, 0x832B, 0x832F, 0x832F, 0x8331, 0x8332, - 0x8334, 0x8336, 0x8338, 0x8339, 0x8340, 0x8340, 0x8345, 0x8345, 0x8349, 0x834A, 0x834F, 0x8350, 0x8352, 0x8352, 0x8358, 0x8358, - 0x8373, 0x8373, 0x8375, 0x8375, 0x8377, 0x8377, 0x837B, 0x837C, 0x8385, 0x8385, 0x8387, 0x8387, 0x8389, 0x838A, 0x838E, 0x838E, - 0x8393, 0x8393, 0x8396, 0x8396, 0x839A, 0x839A, 0x839E, 0x83A0, 0x83A2, 0x83A2, 0x83A8, 0x83A8, 0x83AA, 0x83AB, 0x83B1, 0x83B1, - 0x83B5, 0x83B5, 0x83BD, 0x83BD, 0x83C1, 0x83C1, 0x83C5, 0x83C5, 0x83CA, 0x83CA, 0x83CC, 0x83CC, 0x83CE, 0x83CE, 0x83D3, 0x83D3, - 0x83D6, 0x83D6, 0x83D8, 0x83D8, 0x83DC, 0x83DC, 0x83DF, 0x83E0, 0x83E9, 0x83E9, 0x83EB, 0x83EB, 0x83EF, 0x83F2, 0x83F4, 0x83F4, - 0x83F7, 0x83F7, 0x83FB, 0x83FB, 0x83FD, 0x83FD, 0x8403, 0x8404, 0x8407, 0x8407, 0x840B, 0x840E, 0x8413, 0x8413, 0x8420, 0x8420, - 0x8422, 0x8422, 0x8429, 0x842A, 0x842C, 0x842C, 0x8431, 0x8431, 0x8435, 0x8435, 0x8438, 0x8438, 0x843C, 0x843D, 0x8446, 0x8446, - 0x8449, 0x8449, 0x844E, 0x844E, 0x8457, 0x8457, 0x845B, 0x845B, 0x8461, 0x8463, 0x8466, 0x8466, 0x8469, 0x8469, 0x846B, 0x846F, - 0x8471, 0x8471, 0x8475, 0x8475, 0x8477, 0x8477, 0x8479, 0x847A, 0x8482, 0x8482, 0x8484, 0x8484, 0x848B, 0x848B, 0x8490, 0x8490, - 0x8494, 0x8494, 0x8499, 0x8499, 0x849C, 0x849C, 0x849F, 0x849F, 0x84A1, 0x84A1, 0x84AD, 0x84AD, 0x84B2, 0x84B2, 0x84B8, 0x84B9, - 0x84BB, 0x84BC, 0x84BF, 0x84BF, 0x84C1, 0x84C1, 0x84C4, 0x84C4, 0x84C6, 0x84C6, 0x84C9, 0x84CB, 0x84CD, 0x84CD, 0x84D0, 0x84D1, - 0x84D6, 0x84D6, 0x84D9, 0x84DA, 0x84EC, 0x84EC, 0x84EE, 0x84EE, 0x84F4, 0x84F4, 0x84FC, 0x84FC, 0x84FF, 0x8500, 0x8506, 0x8506, - 0x8511, 0x8511, 0x8513, 0x8515, 0x8517, 0x8518, 0x851A, 0x851A, 0x851F, 0x851F, 0x8521, 0x8521, 0x8526, 0x8526, 0x852C, 0x852D, - 0x8535, 0x8535, 0x853D, 0x853D, 0x8540, 0x8541, 0x8543, 0x8543, 0x8548, 0x854B, 0x854E, 0x854E, 0x8555, 0x8555, 0x8557, 0x8558, - 0x855A, 0x855A, 0x8563, 0x8563, 0x8568, 0x856A, 0x856D, 0x856D, 0x8577, 0x8577, 0x857E, 0x857E, 0x8580, 0x8580, 0x8584, 0x8584, - 0x8587, 0x8588, 0x858A, 0x858A, 0x8590, 0x8591, 0x8594, 0x8594, 0x8597, 0x8597, 0x8599, 0x8599, 0x859B, 0x859C, 0x85A4, 0x85A4, - 0x85A6, 0x85A6, 0x85A8, 0x85AC, 0x85AE, 0x85AF, 0x85B9, 0x85BA, 0x85C1, 0x85C1, 0x85C9, 0x85C9, 0x85CD, 0x85CD, 0x85CF, 0x85D0, - 0x85D5, 0x85D5, 0x85DC, 0x85DD, 0x85E4, 0x85E5, 0x85E9, 0x85EA, 0x85F7, 0x85F7, 0x85F9, 0x85FB, 0x85FE, 0x85FE, 0x8602, 0x8602, - 0x8606, 0x8607, 0x860A, 0x860B, 0x8613, 0x8613, 0x8616, 0x8617, 0x861A, 0x861A, 0x8622, 0x8622, 0x862D, 0x862D, 0x862F, 0x8630, - 0x863F, 0x863F, 0x864D, 0x864E, 0x8650, 0x8650, 0x8654, 0x8655, 0x865A, 0x865A, 0x865C, 0x865C, 0x865E, 0x865F, 0x8667, 0x8667, - 0x866B, 0x866B, 0x8671, 0x8671, 0x8679, 0x8679, 0x867B, 0x867B, 0x868A, 0x868C, 0x8693, 0x8693, 0x8695, 0x8695, 0x86A3, 0x86A4, - 0x86A9, 0x86AB, 0x86AF, 0x86B0, 0x86B6, 0x86B6, 0x86C4, 0x86C4, 0x86C6, 0x86C7, 0x86C9, 0x86C9, 0x86CB, 0x86CB, 0x86CD, 0x86CE, - 0x86D4, 0x86D4, 0x86D9, 0x86D9, 0x86DB, 0x86DB, 0x86DE, 0x86DF, 0x86E4, 0x86E4, 0x86E9, 0x86E9, 0x86EC, 0x86EF, 0x86F8, 0x86F9, - 0x86FB, 0x86FB, 0x86FE, 0x86FE, 0x8700, 0x8700, 0x8702, 0x8703, 0x8706, 0x8706, 0x8708, 0x870A, 0x870D, 0x870D, 0x8711, 0x8712, - 0x8718, 0x8718, 0x871A, 0x871A, 0x871C, 0x871C, 0x8725, 0x8725, 0x8729, 0x8729, 0x8734, 0x8734, 0x8737, 0x8737, 0x873B, 0x873B, - 0x873F, 0x873F, 0x8749, 0x8749, 0x874B, 0x874C, 0x874E, 0x874E, 0x8753, 0x8753, 0x8755, 0x8755, 0x8757, 0x8757, 0x8759, 0x8759, - 0x875F, 0x8760, 0x8763, 0x8763, 0x8766, 0x8766, 0x8768, 0x8768, 0x876A, 0x876A, 0x876E, 0x876E, 0x8774, 0x8774, 0x8776, 0x8776, - 0x8778, 0x8778, 0x877F, 0x877F, 0x8782, 0x8782, 0x878D, 0x878D, 0x879F, 0x879F, 0x87A2, 0x87A2, 0x87AB, 0x87AB, 0x87AF, 0x87AF, - 0x87B3, 0x87B3, 0x87BA, 0x87BB, 0x87BD, 0x87BD, 0x87C0, 0x87C0, 0x87C4, 0x87C4, 0x87C6, 0x87C7, 0x87CB, 0x87CB, 0x87D0, 0x87D0, - 0x87D2, 0x87D2, 0x87E0, 0x87E0, 0x87EF, 0x87EF, 0x87F2, 0x87F2, 0x87F6, 0x87F7, 0x87F9, 0x87F9, 0x87FB, 0x87FB, 0x87FE, 0x87FE, - 0x8805, 0x8805, 0x880D, 0x880F, 0x8811, 0x8811, 0x8815, 0x8816, 0x8821, 0x8823, 0x8827, 0x8827, 0x8831, 0x8831, 0x8836, 0x8836, - 0x8839, 0x8839, 0x883B, 0x883B, 0x8840, 0x8840, 0x8842, 0x8842, 0x8844, 0x8844, 0x8846, 0x8846, 0x884C, 0x884D, 0x8852, 0x8853, - 0x8857, 0x8857, 0x8859, 0x8859, 0x885B, 0x885B, 0x885D, 0x885E, 0x8861, 0x8863, 0x8868, 0x8868, 0x886B, 0x886B, 0x8870, 0x8870, - 0x8872, 0x8872, 0x8875, 0x8875, 0x8877, 0x8877, 0x887D, 0x887F, 0x8881, 0x8882, 0x8888, 0x8888, 0x888B, 0x888B, 0x888D, 0x888D, - 0x8892, 0x8892, 0x8896, 0x8897, 0x8899, 0x8899, 0x889E, 0x889E, 0x88A2, 0x88A2, 0x88A4, 0x88A4, 0x88AB, 0x88AB, 0x88AE, 0x88AE, - 0x88B0, 0x88B1, 0x88B4, 0x88B5, 0x88B7, 0x88B7, 0x88BF, 0x88BF, 0x88C1, 0x88C5, 0x88CF, 0x88CF, 0x88D4, 0x88D5, 0x88D8, 0x88D9, - 0x88DC, 0x88DD, 0x88DF, 0x88DF, 0x88E1, 0x88E1, 0x88E8, 0x88E8, 0x88F2, 0x88F4, 0x88F8, 0x88F9, 0x88FC, 0x88FE, 0x8902, 0x8902, - 0x8904, 0x8904, 0x8907, 0x8907, 0x890A, 0x890A, 0x890C, 0x890C, 0x8910, 0x8910, 0x8912, 0x8913, 0x891D, 0x891E, 0x8925, 0x8925, - 0x892A, 0x892B, 0x8936, 0x8936, 0x8938, 0x8938, 0x893B, 0x893B, 0x8941, 0x8941, 0x8943, 0x8944, 0x894C, 0x894D, 0x8956, 0x8956, - 0x895E, 0x8960, 0x8964, 0x8964, 0x8966, 0x8966, 0x896A, 0x896A, 0x896D, 0x896D, 0x896F, 0x896F, 0x8972, 0x8972, 0x8974, 0x8974, - 0x8977, 0x8977, 0x897E, 0x897F, 0x8981, 0x8981, 0x8983, 0x8983, 0x8986, 0x8988, 0x898A, 0x898B, 0x898F, 0x898F, 0x8993, 0x8993, - 0x8996, 0x8998, 0x899A, 0x899A, 0x89A1, 0x89A1, 0x89A6, 0x89A7, 0x89A9, 0x89AA, 0x89AC, 0x89AC, 0x89AF, 0x89AF, 0x89B2, 0x89B3, - 0x89BA, 0x89BA, 0x89BD, 0x89BD, 0x89BF, 0x89C0, 0x89D2, 0x89D2, 0x89DA, 0x89DA, 0x89DC, 0x89DD, 0x89E3, 0x89E3, 0x89E6, 0x89E7, - 0x89F4, 0x89F4, 0x89F8, 0x89F8, 0x8A00, 0x8A00, 0x8A02, 0x8A03, 0x8A08, 0x8A08, 0x8A0A, 0x8A0A, 0x8A0C, 0x8A0C, 0x8A0E, 0x8A0E, - 0x8A10, 0x8A10, 0x8A13, 0x8A13, 0x8A16, 0x8A18, 0x8A1B, 0x8A1B, 0x8A1D, 0x8A1D, 0x8A1F, 0x8A1F, 0x8A23, 0x8A23, 0x8A25, 0x8A25, - 0x8A2A, 0x8A2A, 0x8A2D, 0x8A2D, 0x8A31, 0x8A31, 0x8A33, 0x8A34, 0x8A36, 0x8A36, 0x8A3A, 0x8A3C, 0x8A41, 0x8A41, 0x8A46, 0x8A46, - 0x8A48, 0x8A48, 0x8A50, 0x8A52, 0x8A54, 0x8A55, 0x8A5B, 0x8A5B, 0x8A5E, 0x8A5E, 0x8A60, 0x8A60, 0x8A62, 0x8A63, 0x8A66, 0x8A66, - 0x8A69, 0x8A69, 0x8A6B, 0x8A6E, 0x8A70, 0x8A73, 0x8A7C, 0x8A7C, 0x8A82, 0x8A82, 0x8A84, 0x8A85, 0x8A87, 0x8A87, 0x8A89, 0x8A89, - 0x8A8C, 0x8A8D, 0x8A91, 0x8A91, 0x8A93, 0x8A93, 0x8A95, 0x8A95, 0x8A98, 0x8A98, 0x8A9A, 0x8A9A, 0x8A9E, 0x8A9E, 0x8AA0, 0x8AA1, - 0x8AA3, 0x8AA6, 0x8AA8, 0x8AA8, 0x8AAC, 0x8AAD, 0x8AB0, 0x8AB0, 0x8AB2, 0x8AB2, 0x8AB9, 0x8AB9, 0x8ABC, 0x8ABC, 0x8ABF, 0x8ABF, - 0x8AC2, 0x8AC2, 0x8AC4, 0x8AC4, 0x8AC7, 0x8AC7, 0x8ACB, 0x8ACD, 0x8ACF, 0x8ACF, 0x8AD2, 0x8AD2, 0x8AD6, 0x8AD6, 0x8ADA, 0x8ADC, - 0x8ADE, 0x8ADE, 0x8AE0, 0x8AE2, 0x8AE4, 0x8AE4, 0x8AE6, 0x8AE7, 0x8AEB, 0x8AEB, 0x8AED, 0x8AEE, 0x8AF1, 0x8AF1, 0x8AF3, 0x8AF3, - 0x8AF7, 0x8AF8, 0x8AFA, 0x8AFA, 0x8AFE, 0x8AFE, 0x8B00, 0x8B02, 0x8B04, 0x8B04, 0x8B07, 0x8B07, 0x8B0C, 0x8B0C, 0x8B0E, 0x8B0E, - 0x8B10, 0x8B10, 0x8B14, 0x8B14, 0x8B16, 0x8B17, 0x8B19, 0x8B1B, 0x8B1D, 0x8B1D, 0x8B20, 0x8B21, 0x8B26, 0x8B26, 0x8B28, 0x8B28, - 0x8B2B, 0x8B2C, 0x8B33, 0x8B33, 0x8B39, 0x8B39, 0x8B3E, 0x8B3E, 0x8B41, 0x8B41, 0x8B49, 0x8B49, 0x8B4C, 0x8B4C, 0x8B4E, 0x8B4F, - 0x8B56, 0x8B56, 0x8B58, 0x8B58, 0x8B5A, 0x8B5C, 0x8B5F, 0x8B5F, 0x8B66, 0x8B66, 0x8B6B, 0x8B6C, 0x8B6F, 0x8B72, 0x8B74, 0x8B74, - 0x8B77, 0x8B77, 0x8B7D, 0x8B7D, 0x8B80, 0x8B80, 0x8B83, 0x8B83, 0x8B8A, 0x8B8A, 0x8B8C, 0x8B8C, 0x8B8E, 0x8B8E, 0x8B90, 0x8B90, - 0x8B92, 0x8B93, 0x8B96, 0x8B96, 0x8B99, 0x8B9A, 0x8C37, 0x8C37, 0x8C3A, 0x8C3A, 0x8C3F, 0x8C3F, 0x8C41, 0x8C41, 0x8C46, 0x8C46, - 0x8C48, 0x8C48, 0x8C4A, 0x8C4A, 0x8C4C, 0x8C4C, 0x8C4E, 0x8C4E, 0x8C50, 0x8C50, 0x8C55, 0x8C55, 0x8C5A, 0x8C5A, 0x8C61, 0x8C62, - 0x8C6A, 0x8C6C, 0x8C78, 0x8C7A, 0x8C7C, 0x8C7C, 0x8C82, 0x8C82, 0x8C85, 0x8C85, 0x8C89, 0x8C8A, 0x8C8C, 0x8C8E, 0x8C94, 0x8C94, - 0x8C98, 0x8C98, 0x8C9D, 0x8C9E, 0x8CA0, 0x8CA2, 0x8CA7, 0x8CB0, 0x8CB2, 0x8CB4, 0x8CB6, 0x8CB8, 0x8CBB, 0x8CBD, 0x8CBF, 0x8CC4, - 0x8CC7, 0x8CC8, 0x8CCA, 0x8CCA, 0x8CCD, 0x8CCE, 0x8CD1, 0x8CD1, 0x8CD3, 0x8CD3, 0x8CDA, 0x8CDC, 0x8CDE, 0x8CDE, 0x8CE0, 0x8CE0, - 0x8CE2, 0x8CE4, 0x8CE6, 0x8CE6, 0x8CEA, 0x8CEA, 0x8CED, 0x8CED, 0x8CFA, 0x8CFD, 0x8D04, 0x8D05, 0x8D07, 0x8D08, 0x8D0A, 0x8D0B, - 0x8D0D, 0x8D0D, 0x8D0F, 0x8D10, 0x8D13, 0x8D14, 0x8D16, 0x8D16, 0x8D64, 0x8D64, 0x8D66, 0x8D67, 0x8D6B, 0x8D6B, 0x8D6D, 0x8D6D, - 0x8D70, 0x8D71, 0x8D73, 0x8D74, 0x8D77, 0x8D77, 0x8D81, 0x8D81, 0x8D85, 0x8D85, 0x8D8A, 0x8D8A, 0x8D99, 0x8D99, 0x8DA3, 0x8DA3, - 0x8DA8, 0x8DA8, 0x8DB3, 0x8DB3, 0x8DBA, 0x8DBA, 0x8DBE, 0x8DBE, 0x8DC2, 0x8DC2, 0x8DCB, 0x8DCC, 0x8DCF, 0x8DCF, 0x8DD6, 0x8DD6, - 0x8DDA, 0x8DDB, 0x8DDD, 0x8DDD, 0x8DDF, 0x8DDF, 0x8DE1, 0x8DE1, 0x8DE3, 0x8DE3, 0x8DE8, 0x8DE8, 0x8DEA, 0x8DEB, 0x8DEF, 0x8DEF, - 0x8DF3, 0x8DF3, 0x8DF5, 0x8DF5, 0x8DFC, 0x8DFC, 0x8DFF, 0x8DFF, 0x8E08, 0x8E0A, 0x8E0F, 0x8E10, 0x8E1D, 0x8E1F, 0x8E2A, 0x8E2A, - 0x8E30, 0x8E30, 0x8E34, 0x8E35, 0x8E42, 0x8E42, 0x8E44, 0x8E44, 0x8E47, 0x8E4A, 0x8E4C, 0x8E4C, 0x8E50, 0x8E50, 0x8E55, 0x8E55, - 0x8E59, 0x8E59, 0x8E5F, 0x8E60, 0x8E63, 0x8E64, 0x8E72, 0x8E72, 0x8E74, 0x8E74, 0x8E76, 0x8E76, 0x8E7C, 0x8E7C, 0x8E81, 0x8E81, - 0x8E84, 0x8E85, 0x8E87, 0x8E87, 0x8E8A, 0x8E8B, 0x8E8D, 0x8E8D, 0x8E91, 0x8E91, 0x8E93, 0x8E94, 0x8E99, 0x8E99, 0x8EA1, 0x8EA1, - 0x8EAA, 0x8EAC, 0x8EAF, 0x8EB1, 0x8EBE, 0x8EBE, 0x8EC5, 0x8EC6, 0x8EC8, 0x8EC8, 0x8ECA, 0x8ECD, 0x8ED2, 0x8ED2, 0x8EDB, 0x8EDB, - 0x8EDF, 0x8EDF, 0x8EE2, 0x8EE3, 0x8EEB, 0x8EEB, 0x8EF8, 0x8EF8, 0x8EFB, 0x8EFE, 0x8F03, 0x8F03, 0x8F05, 0x8F05, 0x8F09, 0x8F0A, - 0x8F0C, 0x8F0C, 0x8F12, 0x8F15, 0x8F19, 0x8F19, 0x8F1B, 0x8F1D, 0x8F1F, 0x8F1F, 0x8F26, 0x8F26, 0x8F29, 0x8F2A, 0x8F2F, 0x8F2F, - 0x8F33, 0x8F33, 0x8F38, 0x8F39, 0x8F3B, 0x8F3B, 0x8F3E, 0x8F3F, 0x8F42, 0x8F42, 0x8F44, 0x8F46, 0x8F49, 0x8F49, 0x8F4C, 0x8F4E, - 0x8F57, 0x8F57, 0x8F5C, 0x8F5C, 0x8F5F, 0x8F5F, 0x8F61, 0x8F64, 0x8F9B, 0x8F9C, 0x8F9E, 0x8F9F, 0x8FA3, 0x8FA3, 0x8FA7, 0x8FA8, - 0x8FAD, 0x8FB2, 0x8FB7, 0x8FB7, 0x8FBA, 0x8FBC, 0x8FBF, 0x8FBF, 0x8FC2, 0x8FC2, 0x8FC4, 0x8FC5, 0x8FCE, 0x8FCE, 0x8FD1, 0x8FD1, - 0x8FD4, 0x8FD4, 0x8FDA, 0x8FDA, 0x8FE2, 0x8FE2, 0x8FE5, 0x8FE6, 0x8FE9, 0x8FEB, 0x8FED, 0x8FED, 0x8FEF, 0x8FF0, 0x8FF4, 0x8FF4, - 0x8FF7, 0x8FFA, 0x8FFD, 0x8FFD, 0x9000, 0x9001, 0x9003, 0x9003, 0x9005, 0x9006, 0x900B, 0x900B, 0x900D, 0x9011, 0x9013, 0x9017, - 0x9019, 0x901A, 0x901D, 0x9023, 0x9027, 0x9027, 0x902E, 0x902E, 0x9031, 0x9032, 0x9035, 0x9036, 0x9038, 0x9039, 0x903C, 0x903C, - 0x903E, 0x903E, 0x9041, 0x9042, 0x9045, 0x9045, 0x9047, 0x9047, 0x9049, 0x904B, 0x904D, 0x9056, 0x9058, 0x9059, 0x905C, 0x905C, - 0x905E, 0x905E, 0x9060, 0x9061, 0x9063, 0x9063, 0x9065, 0x9065, 0x9068, 0x9069, 0x906D, 0x906F, 0x9072, 0x9072, 0x9075, 0x9078, - 0x907A, 0x907A, 0x907C, 0x907D, 0x907F, 0x9084, 0x9087, 0x9087, 0x9089, 0x908A, 0x908F, 0x908F, 0x9091, 0x9091, 0x90A3, 0x90A3, - 0x90A6, 0x90A6, 0x90A8, 0x90A8, 0x90AA, 0x90AA, 0x90AF, 0x90AF, 0x90B1, 0x90B1, 0x90B5, 0x90B5, 0x90B8, 0x90B8, 0x90C1, 0x90C1, - 0x90CA, 0x90CA, 0x90CE, 0x90CE, 0x90DB, 0x90DB, 0x90E1, 0x90E2, 0x90E4, 0x90E4, 0x90E8, 0x90E8, 0x90ED, 0x90ED, 0x90F5, 0x90F5, - 0x90F7, 0x90F7, 0x90FD, 0x90FD, 0x9102, 0x9102, 0x9112, 0x9112, 0x9119, 0x9119, 0x912D, 0x912D, 0x9130, 0x9130, 0x9132, 0x9132, - 0x9149, 0x914E, 0x9152, 0x9152, 0x9154, 0x9154, 0x9156, 0x9156, 0x9158, 0x9158, 0x9162, 0x9163, 0x9165, 0x9165, 0x9169, 0x916A, - 0x916C, 0x916C, 0x9172, 0x9173, 0x9175, 0x9175, 0x9177, 0x9178, 0x9182, 0x9182, 0x9187, 0x9187, 0x9189, 0x9189, 0x918B, 0x918B, - 0x918D, 0x918D, 0x9190, 0x9190, 0x9192, 0x9192, 0x9197, 0x9197, 0x919C, 0x919C, 0x91A2, 0x91A2, 0x91A4, 0x91A4, 0x91AA, 0x91AB, - 0x91AF, 0x91AF, 0x91B4, 0x91B5, 0x91B8, 0x91B8, 0x91BA, 0x91BA, 0x91C0, 0x91C1, 0x91C6, 0x91C9, 0x91CB, 0x91D1, 0x91D6, 0x91D6, - 0x91D8, 0x91D8, 0x91DB, 0x91DD, 0x91DF, 0x91DF, 0x91E1, 0x91E1, 0x91E3, 0x91E3, 0x91E6, 0x91E7, 0x91F5, 0x91F6, 0x91FC, 0x91FC, - 0x91FF, 0x91FF, 0x920D, 0x920E, 0x9211, 0x9211, 0x9214, 0x9215, 0x921E, 0x921E, 0x9229, 0x9229, 0x922C, 0x922C, 0x9234, 0x9234, - 0x9237, 0x9237, 0x923F, 0x923F, 0x9244, 0x9245, 0x9248, 0x9249, 0x924B, 0x924B, 0x9250, 0x9250, 0x9257, 0x9257, 0x925A, 0x925B, - 0x925E, 0x925E, 0x9262, 0x9262, 0x9264, 0x9264, 0x9266, 0x9266, 0x9271, 0x9271, 0x927E, 0x927E, 0x9280, 0x9280, 0x9283, 0x9283, - 0x9285, 0x9285, 0x9291, 0x9291, 0x9293, 0x9293, 0x9295, 0x9296, 0x9298, 0x9298, 0x929A, 0x929C, 0x92AD, 0x92AD, 0x92B7, 0x92B7, - 0x92B9, 0x92B9, 0x92CF, 0x92CF, 0x92D2, 0x92D2, 0x92E4, 0x92E4, 0x92E9, 0x92EA, 0x92ED, 0x92ED, 0x92F2, 0x92F3, 0x92F8, 0x92F8, - 0x92FA, 0x92FA, 0x92FC, 0x92FC, 0x9306, 0x9306, 0x930F, 0x9310, 0x9318, 0x931A, 0x9320, 0x9320, 0x9322, 0x9323, 0x9326, 0x9326, - 0x9328, 0x9328, 0x932B, 0x932C, 0x932E, 0x932F, 0x9332, 0x9332, 0x9335, 0x9335, 0x933A, 0x933B, 0x9344, 0x9344, 0x934B, 0x934B, - 0x934D, 0x934D, 0x9354, 0x9354, 0x9356, 0x9356, 0x935B, 0x935C, 0x9360, 0x9360, 0x936C, 0x936C, 0x936E, 0x936E, 0x9375, 0x9375, - 0x937C, 0x937C, 0x937E, 0x937E, 0x938C, 0x938C, 0x9394, 0x9394, 0x9396, 0x9397, 0x939A, 0x939A, 0x93A7, 0x93A7, 0x93AC, 0x93AE, - 0x93B0, 0x93B0, 0x93B9, 0x93B9, 0x93C3, 0x93C3, 0x93C8, 0x93C8, 0x93D0, 0x93D1, 0x93D6, 0x93D8, 0x93DD, 0x93DD, 0x93E1, 0x93E1, - 0x93E4, 0x93E5, 0x93E8, 0x93E8, 0x9403, 0x9403, 0x9407, 0x9407, 0x9410, 0x9410, 0x9413, 0x9414, 0x9418, 0x941A, 0x9421, 0x9421, - 0x942B, 0x942B, 0x9435, 0x9436, 0x9438, 0x9438, 0x943A, 0x943A, 0x9441, 0x9441, 0x9444, 0x9444, 0x9451, 0x9453, 0x945A, 0x945B, - 0x945E, 0x945E, 0x9460, 0x9460, 0x9462, 0x9462, 0x946A, 0x946A, 0x9470, 0x9470, 0x9475, 0x9475, 0x9477, 0x9477, 0x947C, 0x947F, - 0x9481, 0x9481, 0x9577, 0x9577, 0x9580, 0x9580, 0x9582, 0x9583, 0x9587, 0x9587, 0x9589, 0x958B, 0x958F, 0x958F, 0x9591, 0x9591, - 0x9593, 0x9594, 0x9596, 0x9596, 0x9598, 0x9599, 0x95A0, 0x95A0, 0x95A2, 0x95A5, 0x95A7, 0x95A8, 0x95AD, 0x95AD, 0x95B2, 0x95B2, - 0x95B9, 0x95B9, 0x95BB, 0x95BC, 0x95BE, 0x95BE, 0x95C3, 0x95C3, 0x95C7, 0x95C7, 0x95CA, 0x95CA, 0x95CC, 0x95CD, 0x95D4, 0x95D6, - 0x95D8, 0x95D8, 0x95DC, 0x95DC, 0x95E1, 0x95E2, 0x95E5, 0x95E5, 0x961C, 0x961C, 0x9621, 0x9621, 0x9628, 0x9628, 0x962A, 0x962A, - 0x962E, 0x962F, 0x9632, 0x9632, 0x963B, 0x963B, 0x963F, 0x9640, 0x9642, 0x9642, 0x9644, 0x9644, 0x964B, 0x964D, 0x964F, 0x9650, - 0x965B, 0x965F, 0x9662, 0x9666, 0x966A, 0x966A, 0x966C, 0x966C, 0x9670, 0x9670, 0x9672, 0x9673, 0x9675, 0x9678, 0x967A, 0x967A, - 0x967D, 0x967D, 0x9685, 0x9686, 0x9688, 0x9688, 0x968A, 0x968B, 0x968D, 0x968F, 0x9694, 0x9695, 0x9697, 0x9699, 0x969B, 0x969C, - 0x96A0, 0x96A0, 0x96A3, 0x96A3, 0x96A7, 0x96A8, 0x96AA, 0x96AA, 0x96B0, 0x96B2, 0x96B4, 0x96B4, 0x96B6, 0x96B9, 0x96BB, 0x96BC, - 0x96C0, 0x96C1, 0x96C4, 0x96C7, 0x96C9, 0x96C9, 0x96CB, 0x96CE, 0x96D1, 0x96D1, 0x96D5, 0x96D6, 0x96D9, 0x96D9, 0x96DB, 0x96DC, - 0x96E2, 0x96E3, 0x96E8, 0x96E8, 0x96EA, 0x96EB, 0x96F0, 0x96F0, 0x96F2, 0x96F2, 0x96F6, 0x96F7, 0x96F9, 0x96F9, 0x96FB, 0x96FB, - 0x9700, 0x9700, 0x9704, 0x9704, 0x9706, 0x9708, 0x970A, 0x970A, 0x970D, 0x970F, 0x9711, 0x9711, 0x9713, 0x9713, 0x9716, 0x9716, - 0x9719, 0x9719, 0x971C, 0x971C, 0x971E, 0x971E, 0x9724, 0x9724, 0x9727, 0x9727, 0x972A, 0x972A, 0x9730, 0x9730, 0x9732, 0x9732, - 0x9738, 0x9739, 0x973D, 0x973E, 0x9742, 0x9742, 0x9744, 0x9744, 0x9746, 0x9746, 0x9748, 0x9749, 0x9752, 0x9752, 0x9756, 0x9756, - 0x9759, 0x9759, 0x975C, 0x975C, 0x975E, 0x975E, 0x9760, 0x9762, 0x9764, 0x9764, 0x9766, 0x9766, 0x9768, 0x9769, 0x976B, 0x976B, - 0x976D, 0x976D, 0x9771, 0x9771, 0x9774, 0x9774, 0x9779, 0x977A, 0x977C, 0x977C, 0x9781, 0x9781, 0x9784, 0x9786, 0x978B, 0x978B, - 0x978D, 0x978D, 0x978F, 0x9790, 0x9798, 0x9798, 0x979C, 0x979C, 0x97A0, 0x97A0, 0x97A3, 0x97A3, 0x97A6, 0x97A6, 0x97A8, 0x97A8, - 0x97AB, 0x97AB, 0x97AD, 0x97AD, 0x97B3, 0x97B4, 0x97C3, 0x97C3, 0x97C6, 0x97C6, 0x97C8, 0x97C8, 0x97CB, 0x97CB, 0x97D3, 0x97D3, - 0x97DC, 0x97DC, 0x97ED, 0x97EE, 0x97F2, 0x97F3, 0x97F5, 0x97F6, 0x97FB, 0x97FB, 0x97FF, 0x97FF, 0x9801, 0x9803, 0x9805, 0x9806, - 0x9808, 0x9808, 0x980C, 0x980C, 0x980F, 0x9813, 0x9817, 0x9818, 0x981A, 0x981A, 0x9821, 0x9821, 0x9824, 0x9824, 0x982C, 0x982D, - 0x9834, 0x9834, 0x9837, 0x9838, 0x983B, 0x983D, 0x9846, 0x9846, 0x984B, 0x984F, 0x9854, 0x9855, 0x9858, 0x9858, 0x985B, 0x985B, - 0x985E, 0x985E, 0x9867, 0x9867, 0x986B, 0x986B, 0x986F, 0x9871, 0x9873, 0x9874, 0x98A8, 0x98A8, 0x98AA, 0x98AA, 0x98AF, 0x98AF, - 0x98B1, 0x98B1, 0x98B6, 0x98B6, 0x98C3, 0x98C4, 0x98C6, 0x98C6, 0x98DB, 0x98DC, 0x98DF, 0x98DF, 0x98E2, 0x98E2, 0x98E9, 0x98E9, - 0x98EB, 0x98EB, 0x98ED, 0x98EF, 0x98F2, 0x98F2, 0x98F4, 0x98F4, 0x98FC, 0x98FE, 0x9903, 0x9903, 0x9905, 0x9905, 0x9909, 0x990A, - 0x990C, 0x990C, 0x9910, 0x9910, 0x9912, 0x9914, 0x9918, 0x9918, 0x991D, 0x991E, 0x9920, 0x9921, 0x9924, 0x9924, 0x9928, 0x9928, - 0x992C, 0x992C, 0x992E, 0x992E, 0x993D, 0x993E, 0x9942, 0x9942, 0x9945, 0x9945, 0x9949, 0x9949, 0x994B, 0x994C, 0x9950, 0x9952, - 0x9955, 0x9955, 0x9957, 0x9957, 0x9996, 0x9999, 0x99A5, 0x99A5, 0x99A8, 0x99A8, 0x99AC, 0x99AE, 0x99B3, 0x99B4, 0x99BC, 0x99BC, - 0x99C1, 0x99C1, 0x99C4, 0x99C6, 0x99C8, 0x99C8, 0x99D0, 0x99D2, 0x99D5, 0x99D5, 0x99D8, 0x99D8, 0x99DB, 0x99DB, 0x99DD, 0x99DD, - 0x99DF, 0x99DF, 0x99E2, 0x99E2, 0x99ED, 0x99EE, 0x99F1, 0x99F2, 0x99F8, 0x99F8, 0x99FB, 0x99FB, 0x99FF, 0x99FF, 0x9A01, 0x9A01, - 0x9A05, 0x9A05, 0x9A0E, 0x9A0F, 0x9A12, 0x9A13, 0x9A19, 0x9A19, 0x9A28, 0x9A28, 0x9A2B, 0x9A2B, 0x9A30, 0x9A30, 0x9A37, 0x9A37, - 0x9A3E, 0x9A3E, 0x9A40, 0x9A40, 0x9A42, 0x9A43, 0x9A45, 0x9A45, 0x9A4D, 0x9A4D, 0x9A55, 0x9A55, 0x9A57, 0x9A57, 0x9A5A, 0x9A5B, - 0x9A5F, 0x9A5F, 0x9A62, 0x9A62, 0x9A64, 0x9A65, 0x9A69, 0x9A6B, 0x9AA8, 0x9AA8, 0x9AAD, 0x9AAD, 0x9AB0, 0x9AB0, 0x9AB8, 0x9AB8, - 0x9ABC, 0x9ABC, 0x9AC0, 0x9AC0, 0x9AC4, 0x9AC4, 0x9ACF, 0x9ACF, 0x9AD1, 0x9AD1, 0x9AD3, 0x9AD4, 0x9AD8, 0x9AD8, 0x9ADE, 0x9ADF, - 0x9AE2, 0x9AE3, 0x9AE6, 0x9AE6, 0x9AEA, 0x9AEB, 0x9AED, 0x9AEF, 0x9AF1, 0x9AF1, 0x9AF4, 0x9AF4, 0x9AF7, 0x9AF7, 0x9AFB, 0x9AFB, - 0x9B06, 0x9B06, 0x9B18, 0x9B18, 0x9B1A, 0x9B1A, 0x9B1F, 0x9B1F, 0x9B22, 0x9B23, 0x9B25, 0x9B25, 0x9B27, 0x9B2A, 0x9B2E, 0x9B2F, - 0x9B31, 0x9B32, 0x9B3B, 0x9B3C, 0x9B41, 0x9B45, 0x9B4D, 0x9B4F, 0x9B51, 0x9B51, 0x9B54, 0x9B54, 0x9B58, 0x9B58, 0x9B5A, 0x9B5A, - 0x9B6F, 0x9B6F, 0x9B74, 0x9B74, 0x9B83, 0x9B83, 0x9B8E, 0x9B8E, 0x9B91, 0x9B93, 0x9B96, 0x9B97, 0x9B9F, 0x9BA0, 0x9BA8, 0x9BA8, - 0x9BAA, 0x9BAB, 0x9BAD, 0x9BAE, 0x9BB4, 0x9BB4, 0x9BB9, 0x9BB9, 0x9BC0, 0x9BC0, 0x9BC6, 0x9BC6, 0x9BC9, 0x9BCA, 0x9BCF, 0x9BCF, - 0x9BD1, 0x9BD2, 0x9BD4, 0x9BD4, 0x9BD6, 0x9BD6, 0x9BDB, 0x9BDB, 0x9BE1, 0x9BE4, 0x9BE8, 0x9BE8, 0x9BF0, 0x9BF2, 0x9BF5, 0x9BF5, - 0x9C04, 0x9C04, 0x9C06, 0x9C06, 0x9C08, 0x9C0A, 0x9C0C, 0x9C0D, 0x9C10, 0x9C10, 0x9C12, 0x9C15, 0x9C1B, 0x9C1B, 0x9C21, 0x9C21, - 0x9C24, 0x9C25, 0x9C2D, 0x9C30, 0x9C32, 0x9C32, 0x9C39, 0x9C3B, 0x9C3E, 0x9C3E, 0x9C46, 0x9C48, 0x9C52, 0x9C52, 0x9C57, 0x9C57, - 0x9C5A, 0x9C5A, 0x9C60, 0x9C60, 0x9C67, 0x9C67, 0x9C76, 0x9C76, 0x9C78, 0x9C78, 0x9CE5, 0x9CE5, 0x9CE7, 0x9CE7, 0x9CE9, 0x9CE9, - 0x9CEB, 0x9CEC, 0x9CF0, 0x9CF0, 0x9CF3, 0x9CF4, 0x9CF6, 0x9CF6, 0x9D03, 0x9D03, 0x9D06, 0x9D09, 0x9D0E, 0x9D0E, 0x9D12, 0x9D12, - 0x9D15, 0x9D15, 0x9D1B, 0x9D1B, 0x9D1F, 0x9D1F, 0x9D23, 0x9D23, 0x9D26, 0x9D26, 0x9D28, 0x9D28, 0x9D2A, 0x9D2C, 0x9D3B, 0x9D3B, - 0x9D3E, 0x9D3F, 0x9D41, 0x9D41, 0x9D44, 0x9D44, 0x9D46, 0x9D46, 0x9D48, 0x9D48, 0x9D50, 0x9D51, 0x9D59, 0x9D59, 0x9D5C, 0x9D5E, - 0x9D60, 0x9D61, 0x9D64, 0x9D64, 0x9D6C, 0x9D6C, 0x9D6F, 0x9D6F, 0x9D72, 0x9D72, 0x9D7A, 0x9D7A, 0x9D87, 0x9D87, 0x9D89, 0x9D89, - 0x9D8F, 0x9D8F, 0x9D9A, 0x9D9A, 0x9DA4, 0x9DA4, 0x9DA9, 0x9DA9, 0x9DAB, 0x9DAB, 0x9DAF, 0x9DAF, 0x9DB2, 0x9DB2, 0x9DB4, 0x9DB4, - 0x9DB8, 0x9DB8, 0x9DBA, 0x9DBB, 0x9DC1, 0x9DC2, 0x9DC4, 0x9DC4, 0x9DC6, 0x9DC6, 0x9DCF, 0x9DCF, 0x9DD3, 0x9DD3, 0x9DD9, 0x9DD9, - 0x9DE6, 0x9DE6, 0x9DED, 0x9DED, 0x9DEF, 0x9DEF, 0x9DF2, 0x9DF2, 0x9DF8, 0x9DFA, 0x9DFD, 0x9DFD, 0x9E1A, 0x9E1B, 0x9E1E, 0x9E1E, - 0x9E75, 0x9E75, 0x9E78, 0x9E79, 0x9E7D, 0x9E7D, 0x9E7F, 0x9E7F, 0x9E81, 0x9E81, 0x9E88, 0x9E88, 0x9E8B, 0x9E8C, 0x9E91, 0x9E93, - 0x9E95, 0x9E95, 0x9E97, 0x9E97, 0x9E9D, 0x9E9D, 0x9E9F, 0x9E9F, 0x9EA5, 0x9EA6, 0x9EA9, 0x9EAA, 0x9EAD, 0x9EAD, 0x9EB8, 0x9EBC, - 0x9EBE, 0x9EBF, 0x9EC4, 0x9EC4, 0x9ECC, 0x9ED0, 0x9ED2, 0x9ED2, 0x9ED4, 0x9ED4, 0x9ED8, 0x9ED9, 0x9EDB, 0x9EDE, 0x9EE0, 0x9EE0, - 0x9EE5, 0x9EE5, 0x9EE8, 0x9EE8, 0x9EEF, 0x9EEF, 0x9EF4, 0x9EF4, 0x9EF6, 0x9EF7, 0x9EF9, 0x9EF9, 0x9EFB, 0x9EFD, 0x9F07, 0x9F08, - 0x9F0E, 0x9F0E, 0x9F13, 0x9F13, 0x9F15, 0x9F15, 0x9F20, 0x9F21, 0x9F2C, 0x9F2C, 0x9F3B, 0x9F3B, 0x9F3E, 0x9F3E, 0x9F4A, 0x9F4B, - 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, - }; - } + 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, + 0x223D, 0x223D, 0x2252, 0x2252, 0x2260, 0x2261, 0x2266, 0x2267, 0x226A, 0x226B, 0x2282, 0x2283, 0x2286, 0x2287, 0x22A5, 0x22A5, + 0x2312, 0x2312, 0x2500, 0x2503, 0x250C, 0x250C, 0x250F, 0x2510, 0x2513, 0x2514, 0x2517, 0x2518, 0x251B, 0x251D, 0x2520, 0x2520, + 0x2523, 0x2525, 0x2528, 0x2528, 0x252B, 0x252C, 0x252F, 0x2530, 0x2533, 0x2534, 0x2537, 0x2538, 0x253B, 0x253C, 0x253F, 0x253F, + 0x2542, 0x2542, 0x254B, 0x254B, 0x25A0, 0x25A1, 0x25B2, 0x25B3, 0x25BC, 0x25BD, 0x25C6, 0x25C7, 0x25CB, 0x25CB, 0x25CE, 0x25CF, + 0x25EF, 0x25EF, 0x2605, 0x2606, 0x2640, 0x2640, 0x2642, 0x2642, 0x266A, 0x266A, 0x266D, 0x266D, 0x266F, 0x266F, 0x3000, 0x30FF, + 0x31F0, 0x31FF, 0x4E00, 0x4E01, 0x4E03, 0x4E03, + 0x4E07, 0x4E0B, 0x4E0D, 0x4E0E, 0x4E10, 0x4E11, 0x4E14, 0x4E19, 0x4E1E, 0x4E1E, 0x4E21, 0x4E21, 0x4E26, 0x4E26, 0x4E2A, 0x4E2A, + 0x4E2D, 0x4E2D, 0x4E31, 0x4E32, 0x4E36, 0x4E36, 0x4E38, 0x4E39, 0x4E3B, 0x4E3C, 0x4E3F, 0x4E3F, 0x4E42, 0x4E43, 0x4E45, 0x4E45, + 0x4E4B, 0x4E4B, 0x4E4D, 0x4E4F, 0x4E55, 0x4E59, 0x4E5D, 0x4E5F, 0x4E62, 0x4E62, 0x4E71, 0x4E71, 0x4E73, 0x4E73, 0x4E7E, 0x4E7E, + 0x4E80, 0x4E80, 0x4E82, 0x4E82, 0x4E85, 0x4E86, 0x4E88, 0x4E8C, 0x4E8E, 0x4E8E, 0x4E91, 0x4E92, 0x4E94, 0x4E95, 0x4E98, 0x4E99, + 0x4E9B, 0x4E9C, 0x4E9E, 0x4EA2, 0x4EA4, 0x4EA6, 0x4EA8, 0x4EA8, 0x4EAB, 0x4EAE, 0x4EB0, 0x4EB0, 0x4EB3, 0x4EB3, 0x4EB6, 0x4EB6, + 0x4EBA, 0x4EBA, 0x4EC0, 0x4EC2, 0x4EC4, 0x4EC4, 0x4EC6, 0x4EC7, 0x4ECA, 0x4ECB, 0x4ECD, 0x4ECF, 0x4ED4, 0x4ED9, 0x4EDD, 0x4EDF, + 0x4EE3, 0x4EE5, 0x4EED, 0x4EEE, 0x4EF0, 0x4EF0, 0x4EF2, 0x4EF2, 0x4EF6, 0x4EF7, 0x4EFB, 0x4EFB, 0x4F01, 0x4F01, 0x4F09, 0x4F0A, + 0x4F0D, 0x4F11, 0x4F1A, 0x4F1A, 0x4F1C, 0x4F1D, 0x4F2F, 0x4F30, 0x4F34, 0x4F34, 0x4F36, 0x4F36, 0x4F38, 0x4F38, 0x4F3A, 0x4F3A, + 0x4F3C, 0x4F3D, 0x4F43, 0x4F43, 0x4F46, 0x4F47, 0x4F4D, 0x4F51, 0x4F53, 0x4F53, 0x4F55, 0x4F55, 0x4F57, 0x4F57, 0x4F59, 0x4F5E, + 0x4F69, 0x4F69, 0x4F6F, 0x4F70, 0x4F73, 0x4F73, 0x4F75, 0x4F76, 0x4F7B, 0x4F7C, 0x4F7F, 0x4F7F, 0x4F83, 0x4F83, 0x4F86, 0x4F86, + 0x4F88, 0x4F88, 0x4F8B, 0x4F8B, 0x4F8D, 0x4F8D, 0x4F8F, 0x4F8F, 0x4F91, 0x4F91, 0x4F96, 0x4F96, 0x4F98, 0x4F98, 0x4F9B, 0x4F9B, + 0x4F9D, 0x4F9D, 0x4FA0, 0x4FA1, 0x4FAB, 0x4FAB, 0x4FAD, 0x4FAF, 0x4FB5, 0x4FB6, 0x4FBF, 0x4FBF, 0x4FC2, 0x4FC4, 0x4FCA, 0x4FCA, + 0x4FCE, 0x4FCE, 0x4FD0, 0x4FD1, 0x4FD4, 0x4FD4, 0x4FD7, 0x4FD8, 0x4FDA, 0x4FDB, 0x4FDD, 0x4FDD, 0x4FDF, 0x4FDF, 0x4FE1, 0x4FE1, + 0x4FE3, 0x4FE5, 0x4FEE, 0x4FEF, 0x4FF3, 0x4FF3, 0x4FF5, 0x4FF6, 0x4FF8, 0x4FF8, 0x4FFA, 0x4FFA, 0x4FFE, 0x4FFE, 0x5005, 0x5006, + 0x5009, 0x5009, 0x500B, 0x500B, 0x500D, 0x500D, 0x500F, 0x500F, 0x5011, 0x5012, 0x5014, 0x5014, 0x5016, 0x5016, 0x5019, 0x501A, + 0x501F, 0x501F, 0x5021, 0x5021, 0x5023, 0x5026, 0x5028, 0x502D, 0x5036, 0x5036, 0x5039, 0x5039, 0x5043, 0x5043, 0x5047, 0x5049, + 0x504F, 0x5050, 0x5055, 0x5056, 0x505A, 0x505A, 0x505C, 0x505C, 0x5065, 0x5065, 0x506C, 0x506C, 0x5072, 0x5072, 0x5074, 0x5076, + 0x5078, 0x5078, 0x507D, 0x507D, 0x5080, 0x5080, 0x5085, 0x5085, 0x508D, 0x508D, 0x5091, 0x5091, 0x5098, 0x509A, 0x50AC, 0x50AD, + 0x50B2, 0x50B5, 0x50B7, 0x50B7, 0x50BE, 0x50BE, 0x50C2, 0x50C2, 0x50C5, 0x50C5, 0x50C9, 0x50CA, 0x50CD, 0x50CD, 0x50CF, 0x50CF, + 0x50D1, 0x50D1, 0x50D5, 0x50D6, 0x50DA, 0x50DA, 0x50DE, 0x50DE, 0x50E3, 0x50E3, 0x50E5, 0x50E5, 0x50E7, 0x50E7, 0x50ED, 0x50EE, + 0x50F5, 0x50F5, 0x50F9, 0x50F9, 0x50FB, 0x50FB, 0x5100, 0x5102, 0x5104, 0x5104, 0x5109, 0x5109, 0x5112, 0x5112, 0x5114, 0x5116, + 0x5118, 0x5118, 0x511A, 0x511A, 0x511F, 0x511F, 0x5121, 0x5121, 0x512A, 0x512A, 0x5132, 0x5132, 0x5137, 0x5137, 0x513A, 0x513C, + 0x513F, 0x5141, 0x5143, 0x5149, 0x514B, 0x514E, 0x5150, 0x5150, 0x5152, 0x5152, 0x5154, 0x5154, 0x515A, 0x515A, 0x515C, 0x515C, + 0x5162, 0x5162, 0x5165, 0x5165, 0x5168, 0x516E, 0x5171, 0x5171, 0x5175, 0x5178, 0x517C, 0x517C, 0x5180, 0x5180, 0x5182, 0x5182, + 0x5185, 0x5186, 0x5189, 0x518A, 0x518C, 0x518D, 0x518F, 0x5193, 0x5195, 0x5197, 0x5199, 0x5199, 0x51A0, 0x51A0, 0x51A2, 0x51A2, + 0x51A4, 0x51A6, 0x51A8, 0x51AC, 0x51B0, 0x51B7, 0x51BD, 0x51BD, 0x51C4, 0x51C6, 0x51C9, 0x51C9, 0x51CB, 0x51CD, 0x51D6, 0x51D6, + 0x51DB, 0x51DD, 0x51E0, 0x51E1, 0x51E6, 0x51E7, 0x51E9, 0x51EA, 0x51ED, 0x51ED, 0x51F0, 0x51F1, 0x51F5, 0x51F6, 0x51F8, 0x51FA, + 0x51FD, 0x51FE, 0x5200, 0x5200, 0x5203, 0x5204, 0x5206, 0x5208, 0x520A, 0x520B, 0x520E, 0x520E, 0x5211, 0x5211, 0x5214, 0x5214, + 0x5217, 0x5217, 0x521D, 0x521D, 0x5224, 0x5225, 0x5227, 0x5227, 0x5229, 0x522A, 0x522E, 0x522E, 0x5230, 0x5230, 0x5233, 0x5233, + 0x5236, 0x523B, 0x5243, 0x5244, 0x5247, 0x5247, 0x524A, 0x524D, 0x524F, 0x524F, 0x5254, 0x5254, 0x5256, 0x5256, 0x525B, 0x525B, + 0x525E, 0x525E, 0x5263, 0x5265, 0x5269, 0x526A, 0x526F, 0x5275, 0x527D, 0x527D, 0x527F, 0x527F, 0x5283, 0x5283, 0x5287, 0x5289, + 0x528D, 0x528D, 0x5291, 0x5292, 0x5294, 0x5294, 0x529B, 0x529B, 0x529F, 0x52A0, 0x52A3, 0x52A3, 0x52A9, 0x52AD, 0x52B1, 0x52B1, + 0x52B4, 0x52B5, 0x52B9, 0x52B9, 0x52BC, 0x52BC, 0x52BE, 0x52BE, 0x52C1, 0x52C1, 0x52C3, 0x52C3, 0x52C5, 0x52C5, 0x52C7, 0x52C7, + 0x52C9, 0x52C9, 0x52CD, 0x52CD, 0x52D2, 0x52D2, 0x52D5, 0x52D5, 0x52D7, 0x52D9, 0x52DD, 0x52E0, 0x52E2, 0x52E4, 0x52E6, 0x52E7, + 0x52F2, 0x52F3, 0x52F5, 0x52F5, 0x52F8, 0x52FA, 0x52FE, 0x52FF, 0x5301, 0x5302, 0x5305, 0x5306, 0x5308, 0x5308, 0x530D, 0x530D, + 0x530F, 0x5310, 0x5315, 0x5317, 0x5319, 0x531A, 0x531D, 0x531D, 0x5320, 0x5321, 0x5323, 0x5323, 0x532A, 0x532A, 0x532F, 0x532F, + 0x5331, 0x5331, 0x5333, 0x5333, 0x5338, 0x533B, 0x533F, 0x5341, 0x5343, 0x5343, 0x5345, 0x534A, 0x534D, 0x534D, 0x5351, 0x5354, + 0x5357, 0x5358, 0x535A, 0x535A, 0x535C, 0x535C, 0x535E, 0x535E, 0x5360, 0x5360, 0x5366, 0x5366, 0x5369, 0x5369, 0x536E, 0x5371, + 0x5373, 0x5375, 0x5377, 0x5378, 0x537B, 0x537B, 0x537F, 0x537F, 0x5382, 0x5382, 0x5384, 0x5384, 0x5396, 0x5396, 0x5398, 0x5398, + 0x539A, 0x539A, 0x539F, 0x53A0, 0x53A5, 0x53A6, 0x53A8, 0x53A9, 0x53AD, 0x53AE, 0x53B0, 0x53B0, 0x53B3, 0x53B3, 0x53B6, 0x53B6, + 0x53BB, 0x53BB, 0x53C2, 0x53C3, 0x53C8, 0x53CE, 0x53D4, 0x53D4, 0x53D6, 0x53D7, 0x53D9, 0x53D9, 0x53DB, 0x53DB, 0x53DF, 0x53DF, + 0x53E1, 0x53E5, 0x53E8, 0x53F3, 0x53F6, 0x53F8, 0x53FA, 0x53FA, 0x5401, 0x5401, 0x5403, 0x5404, 0x5408, 0x5411, 0x541B, 0x541B, + 0x541D, 0x541D, 0x541F, 0x5420, 0x5426, 0x5426, 0x5429, 0x5429, 0x542B, 0x542E, 0x5436, 0x5436, 0x5438, 0x5439, 0x543B, 0x543E, + 0x5440, 0x5440, 0x5442, 0x5442, 0x5446, 0x5446, 0x5448, 0x544A, 0x544E, 0x544E, 0x5451, 0x5451, 0x545F, 0x545F, 0x5468, 0x5468, + 0x546A, 0x546A, 0x5470, 0x5471, 0x5473, 0x5473, 0x5475, 0x5477, 0x547B, 0x547D, 0x5480, 0x5480, 0x5484, 0x5484, 0x5486, 0x5486, + 0x548B, 0x548C, 0x548E, 0x5490, 0x5492, 0x5492, 0x54A2, 0x54A2, 0x54A4, 0x54A5, 0x54A8, 0x54A8, 0x54AB, 0x54AC, 0x54AF, 0x54AF, + 0x54B2, 0x54B3, 0x54B8, 0x54B8, 0x54BC, 0x54BE, 0x54C0, 0x54C2, 0x54C4, 0x54C4, 0x54C7, 0x54C9, 0x54D8, 0x54D8, 0x54E1, 0x54E2, + 0x54E5, 0x54E6, 0x54E8, 0x54E9, 0x54ED, 0x54EE, 0x54F2, 0x54F2, 0x54FA, 0x54FA, 0x54FD, 0x54FD, 0x5504, 0x5504, 0x5506, 0x5507, + 0x550F, 0x5510, 0x5514, 0x5514, 0x5516, 0x5516, 0x552E, 0x552F, 0x5531, 0x5531, 0x5533, 0x5533, 0x5538, 0x5539, 0x553E, 0x553E, + 0x5540, 0x5540, 0x5544, 0x5546, 0x554C, 0x554C, 0x554F, 0x554F, 0x5553, 0x5553, 0x5556, 0x5557, 0x555C, 0x555D, 0x5563, 0x5563, + 0x557B, 0x557C, 0x557E, 0x557E, 0x5580, 0x5580, 0x5583, 0x5584, 0x5587, 0x5587, 0x5589, 0x558B, 0x5598, 0x559A, 0x559C, 0x559F, + 0x55A7, 0x55AC, 0x55AE, 0x55AE, 0x55B0, 0x55B0, 0x55B6, 0x55B6, 0x55C4, 0x55C5, 0x55C7, 0x55C7, 0x55D4, 0x55D4, 0x55DA, 0x55DA, + 0x55DC, 0x55DC, 0x55DF, 0x55DF, 0x55E3, 0x55E4, 0x55F7, 0x55F7, 0x55F9, 0x55F9, 0x55FD, 0x55FE, 0x5606, 0x5606, 0x5609, 0x5609, + 0x5614, 0x5614, 0x5616, 0x5618, 0x561B, 0x561B, 0x5629, 0x5629, 0x562F, 0x562F, 0x5631, 0x5632, 0x5634, 0x5634, 0x5636, 0x5636, + 0x5638, 0x5638, 0x5642, 0x5642, 0x564C, 0x564C, 0x564E, 0x564E, 0x5650, 0x5650, 0x565B, 0x565B, 0x5664, 0x5664, 0x5668, 0x5668, + 0x566A, 0x566C, 0x5674, 0x5674, 0x5678, 0x5678, 0x567A, 0x567A, 0x5680, 0x5680, 0x5686, 0x5687, 0x568A, 0x568A, 0x568F, 0x568F, + 0x5694, 0x5694, 0x56A0, 0x56A0, 0x56A2, 0x56A2, 0x56A5, 0x56A5, 0x56AE, 0x56AE, 0x56B4, 0x56B4, 0x56B6, 0x56B6, 0x56BC, 0x56BC, + 0x56C0, 0x56C3, 0x56C8, 0x56C8, 0x56CE, 0x56CE, 0x56D1, 0x56D1, 0x56D3, 0x56D3, 0x56D7, 0x56D8, 0x56DA, 0x56DB, 0x56DE, 0x56DE, + 0x56E0, 0x56E0, 0x56E3, 0x56E3, 0x56EE, 0x56EE, 0x56F0, 0x56F0, 0x56F2, 0x56F3, 0x56F9, 0x56FA, 0x56FD, 0x56FD, 0x56FF, 0x5700, + 0x5703, 0x5704, 0x5708, 0x5709, 0x570B, 0x570B, 0x570D, 0x570D, 0x570F, 0x570F, 0x5712, 0x5713, 0x5716, 0x5716, 0x5718, 0x5718, + 0x571C, 0x571C, 0x571F, 0x571F, 0x5726, 0x5728, 0x572D, 0x572D, 0x5730, 0x5730, 0x5737, 0x5738, 0x573B, 0x573B, 0x5740, 0x5740, + 0x5742, 0x5742, 0x5747, 0x5747, 0x574A, 0x574A, 0x574E, 0x5751, 0x5761, 0x5761, 0x5764, 0x5764, 0x5766, 0x5766, 0x5769, 0x576A, + 0x577F, 0x577F, 0x5782, 0x5782, 0x5788, 0x5789, 0x578B, 0x578B, 0x5793, 0x5793, 0x57A0, 0x57A0, 0x57A2, 0x57A4, 0x57AA, 0x57AA, + 0x57B0, 0x57B0, 0x57B3, 0x57B3, 0x57C0, 0x57C0, 0x57C3, 0x57C3, 0x57C6, 0x57C6, 0x57CB, 0x57CB, 0x57CE, 0x57CE, 0x57D2, 0x57D4, + 0x57D6, 0x57D6, 0x57DC, 0x57DC, 0x57DF, 0x57E0, 0x57E3, 0x57E3, 0x57F4, 0x57F4, 0x57F7, 0x57F7, 0x57F9, 0x57FA, 0x57FC, 0x57FC, + 0x5800, 0x5800, 0x5802, 0x5802, 0x5805, 0x5806, 0x580A, 0x580B, 0x5815, 0x5815, 0x5819, 0x5819, 0x581D, 0x581D, 0x5821, 0x5821, + 0x5824, 0x5824, 0x582A, 0x582A, 0x582F, 0x5831, 0x5834, 0x5835, 0x583A, 0x583A, 0x583D, 0x583D, 0x5840, 0x5841, 0x584A, 0x584B, + 0x5851, 0x5852, 0x5854, 0x5854, 0x5857, 0x585A, 0x585E, 0x585E, 0x5862, 0x5862, 0x5869, 0x5869, 0x586B, 0x586B, 0x5870, 0x5870, + 0x5872, 0x5872, 0x5875, 0x5875, 0x5879, 0x5879, 0x587E, 0x587E, 0x5883, 0x5883, 0x5885, 0x5885, 0x5893, 0x5893, 0x5897, 0x5897, + 0x589C, 0x589C, 0x589F, 0x589F, 0x58A8, 0x58A8, 0x58AB, 0x58AB, 0x58AE, 0x58AE, 0x58B3, 0x58B3, 0x58B8, 0x58BB, 0x58BE, 0x58BE, + 0x58C1, 0x58C1, 0x58C5, 0x58C5, 0x58C7, 0x58C7, 0x58CA, 0x58CA, 0x58CC, 0x58CC, 0x58D1, 0x58D1, 0x58D3, 0x58D3, 0x58D5, 0x58D5, + 0x58D7, 0x58D9, 0x58DC, 0x58DC, 0x58DE, 0x58DF, 0x58E4, 0x58E5, 0x58EB, 0x58EC, 0x58EE, 0x58F2, 0x58F7, 0x58F7, 0x58F9, 0x58FD, + 0x5902, 0x5902, 0x5909, 0x590A, 0x590F, 0x5910, 0x5915, 0x5916, 0x5918, 0x591C, 0x5922, 0x5922, 0x5925, 0x5925, 0x5927, 0x5927, + 0x5929, 0x592E, 0x5931, 0x5932, 0x5937, 0x5938, 0x593E, 0x593E, 0x5944, 0x5944, 0x5947, 0x5949, 0x594E, 0x5951, 0x5954, 0x5955, + 0x5957, 0x5958, 0x595A, 0x595A, 0x5960, 0x5960, 0x5962, 0x5962, 0x5965, 0x5965, 0x5967, 0x596A, 0x596C, 0x596C, 0x596E, 0x596E, + 0x5973, 0x5974, 0x5978, 0x5978, 0x597D, 0x597D, 0x5981, 0x5984, 0x598A, 0x598A, 0x598D, 0x598D, 0x5993, 0x5993, 0x5996, 0x5996, + 0x5999, 0x5999, 0x599B, 0x599B, 0x599D, 0x599D, 0x59A3, 0x59A3, 0x59A5, 0x59A5, 0x59A8, 0x59A8, 0x59AC, 0x59AC, 0x59B2, 0x59B2, + 0x59B9, 0x59B9, 0x59BB, 0x59BB, 0x59BE, 0x59BE, 0x59C6, 0x59C6, 0x59C9, 0x59C9, 0x59CB, 0x59CB, 0x59D0, 0x59D1, 0x59D3, 0x59D4, + 0x59D9, 0x59DA, 0x59DC, 0x59DC, 0x59E5, 0x59E6, 0x59E8, 0x59E8, 0x59EA, 0x59EB, 0x59F6, 0x59F6, 0x59FB, 0x59FB, 0x59FF, 0x59FF, + 0x5A01, 0x5A01, 0x5A03, 0x5A03, 0x5A09, 0x5A09, 0x5A11, 0x5A11, 0x5A18, 0x5A18, 0x5A1A, 0x5A1A, 0x5A1C, 0x5A1C, 0x5A1F, 0x5A20, + 0x5A25, 0x5A25, 0x5A29, 0x5A29, 0x5A2F, 0x5A2F, 0x5A35, 0x5A36, 0x5A3C, 0x5A3C, 0x5A40, 0x5A41, 0x5A46, 0x5A46, 0x5A49, 0x5A49, + 0x5A5A, 0x5A5A, 0x5A62, 0x5A62, 0x5A66, 0x5A66, 0x5A6A, 0x5A6A, 0x5A6C, 0x5A6C, 0x5A7F, 0x5A7F, 0x5A92, 0x5A92, 0x5A9A, 0x5A9B, + 0x5ABC, 0x5ABE, 0x5AC1, 0x5AC2, 0x5AC9, 0x5AC9, 0x5ACB, 0x5ACC, 0x5AD0, 0x5AD0, 0x5AD6, 0x5AD7, 0x5AE1, 0x5AE1, 0x5AE3, 0x5AE3, + 0x5AE6, 0x5AE6, 0x5AE9, 0x5AE9, 0x5AFA, 0x5AFB, 0x5B09, 0x5B09, 0x5B0B, 0x5B0C, 0x5B16, 0x5B16, 0x5B22, 0x5B22, 0x5B2A, 0x5B2A, + 0x5B2C, 0x5B2C, 0x5B30, 0x5B30, 0x5B32, 0x5B32, 0x5B36, 0x5B36, 0x5B3E, 0x5B3E, 0x5B40, 0x5B40, 0x5B43, 0x5B43, 0x5B45, 0x5B45, + 0x5B50, 0x5B51, 0x5B54, 0x5B55, 0x5B57, 0x5B58, 0x5B5A, 0x5B5D, 0x5B5F, 0x5B5F, 0x5B63, 0x5B66, 0x5B69, 0x5B69, 0x5B6B, 0x5B6B, + 0x5B70, 0x5B71, 0x5B73, 0x5B73, 0x5B75, 0x5B75, 0x5B78, 0x5B78, 0x5B7A, 0x5B7A, 0x5B80, 0x5B80, 0x5B83, 0x5B83, 0x5B85, 0x5B85, + 0x5B87, 0x5B89, 0x5B8B, 0x5B8D, 0x5B8F, 0x5B8F, 0x5B95, 0x5B95, 0x5B97, 0x5B9D, 0x5B9F, 0x5B9F, 0x5BA2, 0x5BA6, 0x5BAE, 0x5BAE, + 0x5BB0, 0x5BB0, 0x5BB3, 0x5BB6, 0x5BB8, 0x5BB9, 0x5BBF, 0x5BBF, 0x5BC2, 0x5BC7, 0x5BC9, 0x5BC9, 0x5BCC, 0x5BCC, 0x5BD0, 0x5BD0, + 0x5BD2, 0x5BD4, 0x5BDB, 0x5BDB, 0x5BDD, 0x5BDF, 0x5BE1, 0x5BE2, 0x5BE4, 0x5BE9, 0x5BEB, 0x5BEB, 0x5BEE, 0x5BEE, 0x5BF0, 0x5BF0, + 0x5BF3, 0x5BF3, 0x5BF5, 0x5BF6, 0x5BF8, 0x5BF8, 0x5BFA, 0x5BFA, 0x5BFE, 0x5BFF, 0x5C01, 0x5C02, 0x5C04, 0x5C0B, 0x5C0D, 0x5C0F, + 0x5C11, 0x5C11, 0x5C13, 0x5C13, 0x5C16, 0x5C16, 0x5C1A, 0x5C1A, 0x5C20, 0x5C20, 0x5C22, 0x5C22, 0x5C24, 0x5C24, 0x5C28, 0x5C28, + 0x5C2D, 0x5C2D, 0x5C31, 0x5C31, 0x5C38, 0x5C41, 0x5C45, 0x5C46, 0x5C48, 0x5C48, 0x5C4A, 0x5C4B, 0x5C4D, 0x5C51, 0x5C53, 0x5C53, + 0x5C55, 0x5C55, 0x5C5E, 0x5C5E, 0x5C60, 0x5C61, 0x5C64, 0x5C65, 0x5C6C, 0x5C6C, 0x5C6E, 0x5C6F, 0x5C71, 0x5C71, 0x5C76, 0x5C76, + 0x5C79, 0x5C79, 0x5C8C, 0x5C8C, 0x5C90, 0x5C91, 0x5C94, 0x5C94, 0x5CA1, 0x5CA1, 0x5CA8, 0x5CA9, 0x5CAB, 0x5CAC, 0x5CB1, 0x5CB1, + 0x5CB3, 0x5CB3, 0x5CB6, 0x5CB8, 0x5CBB, 0x5CBC, 0x5CBE, 0x5CBE, 0x5CC5, 0x5CC5, 0x5CC7, 0x5CC7, 0x5CD9, 0x5CD9, 0x5CE0, 0x5CE1, + 0x5CE8, 0x5CEA, 0x5CED, 0x5CED, 0x5CEF, 0x5CF0, 0x5CF6, 0x5CF6, 0x5CFA, 0x5CFB, 0x5CFD, 0x5CFD, 0x5D07, 0x5D07, 0x5D0B, 0x5D0B, + 0x5D0E, 0x5D0E, 0x5D11, 0x5D11, 0x5D14, 0x5D1B, 0x5D1F, 0x5D1F, 0x5D22, 0x5D22, 0x5D29, 0x5D29, 0x5D4B, 0x5D4C, 0x5D4E, 0x5D4E, + 0x5D50, 0x5D50, 0x5D52, 0x5D52, 0x5D5C, 0x5D5C, 0x5D69, 0x5D69, 0x5D6C, 0x5D6C, 0x5D6F, 0x5D6F, 0x5D73, 0x5D73, 0x5D76, 0x5D76, + 0x5D82, 0x5D82, 0x5D84, 0x5D84, 0x5D87, 0x5D87, 0x5D8B, 0x5D8C, 0x5D90, 0x5D90, 0x5D9D, 0x5D9D, 0x5DA2, 0x5DA2, 0x5DAC, 0x5DAC, + 0x5DAE, 0x5DAE, 0x5DB7, 0x5DB7, 0x5DBA, 0x5DBA, 0x5DBC, 0x5DBD, 0x5DC9, 0x5DC9, 0x5DCC, 0x5DCD, 0x5DD2, 0x5DD3, 0x5DD6, 0x5DD6, + 0x5DDB, 0x5DDB, 0x5DDD, 0x5DDE, 0x5DE1, 0x5DE1, 0x5DE3, 0x5DE3, 0x5DE5, 0x5DE8, 0x5DEB, 0x5DEB, 0x5DEE, 0x5DEE, 0x5DF1, 0x5DF5, + 0x5DF7, 0x5DF7, 0x5DFB, 0x5DFB, 0x5DFD, 0x5DFE, 0x5E02, 0x5E03, 0x5E06, 0x5E06, 0x5E0B, 0x5E0C, 0x5E11, 0x5E11, 0x5E16, 0x5E16, + 0x5E19, 0x5E1B, 0x5E1D, 0x5E1D, 0x5E25, 0x5E25, 0x5E2B, 0x5E2B, 0x5E2D, 0x5E2D, 0x5E2F, 0x5E30, 0x5E33, 0x5E33, 0x5E36, 0x5E38, + 0x5E3D, 0x5E3D, 0x5E40, 0x5E40, 0x5E43, 0x5E45, 0x5E47, 0x5E47, 0x5E4C, 0x5E4C, 0x5E4E, 0x5E4E, 0x5E54, 0x5E55, 0x5E57, 0x5E57, + 0x5E5F, 0x5E5F, 0x5E61, 0x5E64, 0x5E72, 0x5E76, 0x5E78, 0x5E7F, 0x5E81, 0x5E81, 0x5E83, 0x5E84, 0x5E87, 0x5E87, 0x5E8A, 0x5E8A, + 0x5E8F, 0x5E8F, 0x5E95, 0x5E97, 0x5E9A, 0x5E9A, 0x5E9C, 0x5E9C, 0x5EA0, 0x5EA0, 0x5EA6, 0x5EA7, 0x5EAB, 0x5EAB, 0x5EAD, 0x5EAD, + 0x5EB5, 0x5EB8, 0x5EC1, 0x5EC3, 0x5EC8, 0x5ECA, 0x5ECF, 0x5ED0, 0x5ED3, 0x5ED3, 0x5ED6, 0x5ED6, 0x5EDA, 0x5EDB, 0x5EDD, 0x5EDD, + 0x5EDF, 0x5EE3, 0x5EE8, 0x5EE9, 0x5EEC, 0x5EEC, 0x5EF0, 0x5EF1, 0x5EF3, 0x5EF4, 0x5EF6, 0x5EF8, 0x5EFA, 0x5EFC, 0x5EFE, 0x5EFF, + 0x5F01, 0x5F01, 0x5F03, 0x5F04, 0x5F09, 0x5F0D, 0x5F0F, 0x5F11, 0x5F13, 0x5F18, 0x5F1B, 0x5F1B, 0x5F1F, 0x5F1F, 0x5F25, 0x5F27, + 0x5F29, 0x5F29, 0x5F2D, 0x5F2D, 0x5F2F, 0x5F2F, 0x5F31, 0x5F31, 0x5F35, 0x5F35, 0x5F37, 0x5F38, 0x5F3C, 0x5F3C, 0x5F3E, 0x5F3E, + 0x5F41, 0x5F41, 0x5F48, 0x5F48, 0x5F4A, 0x5F4A, 0x5F4C, 0x5F4C, 0x5F4E, 0x5F4E, 0x5F51, 0x5F51, 0x5F53, 0x5F53, 0x5F56, 0x5F57, + 0x5F59, 0x5F59, 0x5F5C, 0x5F5D, 0x5F61, 0x5F62, 0x5F66, 0x5F66, 0x5F69, 0x5F6D, 0x5F70, 0x5F71, 0x5F73, 0x5F73, 0x5F77, 0x5F77, + 0x5F79, 0x5F79, 0x5F7C, 0x5F7C, 0x5F7F, 0x5F85, 0x5F87, 0x5F88, 0x5F8A, 0x5F8C, 0x5F90, 0x5F93, 0x5F97, 0x5F99, 0x5F9E, 0x5F9E, + 0x5FA0, 0x5FA1, 0x5FA8, 0x5FAA, 0x5FAD, 0x5FAE, 0x5FB3, 0x5FB4, 0x5FB9, 0x5FB9, 0x5FBC, 0x5FBD, 0x5FC3, 0x5FC3, 0x5FC5, 0x5FC5, + 0x5FCC, 0x5FCD, 0x5FD6, 0x5FD9, 0x5FDC, 0x5FDD, 0x5FE0, 0x5FE0, 0x5FE4, 0x5FE4, 0x5FEB, 0x5FEB, 0x5FF0, 0x5FF1, 0x5FF5, 0x5FF5, + 0x5FF8, 0x5FF8, 0x5FFB, 0x5FFB, 0x5FFD, 0x5FFD, 0x5FFF, 0x5FFF, 0x600E, 0x6010, 0x6012, 0x6012, 0x6015, 0x6016, 0x6019, 0x6019, + 0x601B, 0x601D, 0x6020, 0x6021, 0x6025, 0x602B, 0x602F, 0x602F, 0x6031, 0x6031, 0x603A, 0x603A, 0x6041, 0x6043, 0x6046, 0x6046, + 0x604A, 0x604B, 0x604D, 0x604D, 0x6050, 0x6050, 0x6052, 0x6052, 0x6055, 0x6055, 0x6059, 0x605A, 0x605F, 0x6060, 0x6062, 0x6065, + 0x6068, 0x606D, 0x606F, 0x6070, 0x6075, 0x6075, 0x6077, 0x6077, 0x6081, 0x6081, 0x6083, 0x6084, 0x6089, 0x6089, 0x608B, 0x608D, + 0x6092, 0x6092, 0x6094, 0x6094, 0x6096, 0x6097, 0x609A, 0x609B, 0x609F, 0x60A0, 0x60A3, 0x60A3, 0x60A6, 0x60A7, 0x60A9, 0x60AA, + 0x60B2, 0x60B6, 0x60B8, 0x60B8, 0x60BC, 0x60BD, 0x60C5, 0x60C7, 0x60D1, 0x60D1, 0x60D3, 0x60D3, 0x60D8, 0x60D8, 0x60DA, 0x60DA, + 0x60DC, 0x60DC, 0x60DF, 0x60E1, 0x60E3, 0x60E3, 0x60E7, 0x60E8, 0x60F0, 0x60F1, 0x60F3, 0x60F4, 0x60F6, 0x60F7, 0x60F9, 0x60FB, + 0x6100, 0x6101, 0x6103, 0x6103, 0x6106, 0x6106, 0x6108, 0x6109, 0x610D, 0x610F, 0x6115, 0x6115, 0x611A, 0x611B, 0x611F, 0x611F, + 0x6121, 0x6121, 0x6127, 0x6128, 0x612C, 0x612C, 0x6134, 0x6134, 0x613C, 0x613F, 0x6142, 0x6142, 0x6144, 0x6144, 0x6147, 0x6148, + 0x614A, 0x614E, 0x6153, 0x6153, 0x6155, 0x6155, 0x6158, 0x615A, 0x615D, 0x615D, 0x615F, 0x615F, 0x6162, 0x6163, 0x6165, 0x6165, + 0x6167, 0x6168, 0x616B, 0x616B, 0x616E, 0x6171, 0x6173, 0x6177, 0x617E, 0x617E, 0x6182, 0x6182, 0x6187, 0x6187, 0x618A, 0x618A, + 0x618E, 0x618E, 0x6190, 0x6191, 0x6194, 0x6194, 0x6196, 0x6196, 0x6199, 0x619A, 0x61A4, 0x61A4, 0x61A7, 0x61A7, 0x61A9, 0x61A9, + 0x61AB, 0x61AC, 0x61AE, 0x61AE, 0x61B2, 0x61B2, 0x61B6, 0x61B6, 0x61BA, 0x61BA, 0x61BE, 0x61BE, 0x61C3, 0x61C3, 0x61C6, 0x61CD, + 0x61D0, 0x61D0, 0x61E3, 0x61E3, 0x61E6, 0x61E6, 0x61F2, 0x61F2, 0x61F4, 0x61F4, 0x61F6, 0x61F8, 0x61FA, 0x61FA, 0x61FC, 0x6200, + 0x6208, 0x620A, 0x620C, 0x620E, 0x6210, 0x6212, 0x6214, 0x6214, 0x6216, 0x6216, 0x621A, 0x621B, 0x621D, 0x621F, 0x6221, 0x6221, + 0x6226, 0x6226, 0x622A, 0x622A, 0x622E, 0x6230, 0x6232, 0x6234, 0x6238, 0x6238, 0x623B, 0x623B, 0x623F, 0x6241, 0x6247, 0x6249, + 0x624B, 0x624B, 0x624D, 0x624E, 0x6253, 0x6253, 0x6255, 0x6255, 0x6258, 0x6258, 0x625B, 0x625B, 0x625E, 0x625E, 0x6260, 0x6260, + 0x6263, 0x6263, 0x6268, 0x6268, 0x626E, 0x626E, 0x6271, 0x6271, 0x6276, 0x6276, 0x6279, 0x6279, 0x627C, 0x627C, 0x627E, 0x6280, + 0x6282, 0x6284, 0x6289, 0x628A, 0x6291, 0x6298, 0x629B, 0x629C, 0x629E, 0x629E, 0x62AB, 0x62AC, 0x62B1, 0x62B1, 0x62B5, 0x62B5, + 0x62B9, 0x62B9, 0x62BB, 0x62BD, 0x62C2, 0x62C2, 0x62C5, 0x62CA, 0x62CC, 0x62CD, 0x62CF, 0x62D4, 0x62D7, 0x62D9, 0x62DB, 0x62DD, + 0x62E0, 0x62E1, 0x62EC, 0x62EF, 0x62F1, 0x62F1, 0x62F3, 0x62F3, 0x62F5, 0x62F7, 0x62FE, 0x62FF, 0x6301, 0x6302, 0x6307, 0x6309, + 0x630C, 0x630C, 0x6311, 0x6311, 0x6319, 0x6319, 0x631F, 0x631F, 0x6327, 0x6328, 0x632B, 0x632B, 0x632F, 0x632F, 0x633A, 0x633A, + 0x633D, 0x633F, 0x6349, 0x6349, 0x634C, 0x634D, 0x634F, 0x6350, 0x6355, 0x6355, 0x6357, 0x6357, 0x635C, 0x635C, 0x6367, 0x6369, + 0x636B, 0x636B, 0x636E, 0x636E, 0x6372, 0x6372, 0x6376, 0x6377, 0x637A, 0x637B, 0x6380, 0x6380, 0x6383, 0x6383, 0x6388, 0x6389, + 0x638C, 0x638C, 0x638E, 0x638F, 0x6392, 0x6392, 0x6396, 0x6396, 0x6398, 0x6398, 0x639B, 0x639B, 0x639F, 0x63A3, 0x63A5, 0x63A5, + 0x63A7, 0x63AC, 0x63B2, 0x63B2, 0x63B4, 0x63B5, 0x63BB, 0x63BB, 0x63BE, 0x63BE, 0x63C0, 0x63C0, 0x63C3, 0x63C4, 0x63C6, 0x63C6, + 0x63C9, 0x63C9, 0x63CF, 0x63D0, 0x63D2, 0x63D2, 0x63D6, 0x63D6, 0x63DA, 0x63DB, 0x63E1, 0x63E1, 0x63E3, 0x63E3, 0x63E9, 0x63E9, + 0x63EE, 0x63EE, 0x63F4, 0x63F4, 0x63F6, 0x63F6, 0x63FA, 0x63FA, 0x6406, 0x6406, 0x640D, 0x640D, 0x640F, 0x640F, 0x6413, 0x6413, + 0x6416, 0x6417, 0x641C, 0x641C, 0x6426, 0x6426, 0x6428, 0x6428, 0x642C, 0x642D, 0x6434, 0x6434, 0x6436, 0x6436, 0x643A, 0x643A, + 0x643E, 0x643E, 0x6442, 0x6442, 0x644E, 0x644E, 0x6458, 0x6458, 0x6467, 0x6467, 0x6469, 0x6469, 0x646F, 0x646F, 0x6476, 0x6476, + 0x6478, 0x6478, 0x647A, 0x647A, 0x6483, 0x6483, 0x6488, 0x6488, 0x6492, 0x6493, 0x6495, 0x6495, 0x649A, 0x649A, 0x649E, 0x649E, + 0x64A4, 0x64A5, 0x64A9, 0x64A9, 0x64AB, 0x64AB, 0x64AD, 0x64AE, 0x64B0, 0x64B0, 0x64B2, 0x64B2, 0x64B9, 0x64B9, 0x64BB, 0x64BC, + 0x64C1, 0x64C2, 0x64C5, 0x64C5, 0x64C7, 0x64C7, 0x64CD, 0x64CD, 0x64D2, 0x64D2, 0x64D4, 0x64D4, 0x64D8, 0x64D8, 0x64DA, 0x64DA, + 0x64E0, 0x64E3, 0x64E6, 0x64E7, 0x64EC, 0x64EC, 0x64EF, 0x64EF, 0x64F1, 0x64F2, 0x64F4, 0x64F4, 0x64F6, 0x64F6, 0x64FA, 0x64FA, + 0x64FD, 0x64FE, 0x6500, 0x6500, 0x6505, 0x6505, 0x6518, 0x6518, 0x651C, 0x651D, 0x6523, 0x6524, 0x652A, 0x652C, 0x652F, 0x652F, + 0x6534, 0x6539, 0x653B, 0x653B, 0x653E, 0x653F, 0x6545, 0x6545, 0x6548, 0x6548, 0x654D, 0x654D, 0x654F, 0x654F, 0x6551, 0x6551, + 0x6555, 0x6559, 0x655D, 0x655E, 0x6562, 0x6563, 0x6566, 0x6566, 0x656C, 0x656C, 0x6570, 0x6570, 0x6572, 0x6572, 0x6574, 0x6575, + 0x6577, 0x6578, 0x6582, 0x6583, 0x6587, 0x6589, 0x658C, 0x658C, 0x658E, 0x658E, 0x6590, 0x6591, 0x6597, 0x6597, 0x6599, 0x6599, + 0x659B, 0x659C, 0x659F, 0x659F, 0x65A1, 0x65A1, 0x65A4, 0x65A5, 0x65A7, 0x65A7, 0x65AB, 0x65AD, 0x65AF, 0x65B0, 0x65B7, 0x65B7, + 0x65B9, 0x65B9, 0x65BC, 0x65BD, 0x65C1, 0x65C1, 0x65C3, 0x65C6, 0x65CB, 0x65CC, 0x65CF, 0x65CF, 0x65D2, 0x65D2, 0x65D7, 0x65D7, + 0x65D9, 0x65D9, 0x65DB, 0x65DB, 0x65E0, 0x65E2, 0x65E5, 0x65E9, 0x65EC, 0x65ED, 0x65F1, 0x65F1, 0x65FA, 0x65FB, 0x6602, 0x6603, + 0x6606, 0x6607, 0x660A, 0x660A, 0x660C, 0x660C, 0x660E, 0x660F, 0x6613, 0x6614, 0x661C, 0x661C, 0x661F, 0x6620, 0x6625, 0x6625, + 0x6627, 0x6628, 0x662D, 0x662D, 0x662F, 0x662F, 0x6634, 0x6636, 0x663C, 0x663C, 0x663F, 0x663F, 0x6641, 0x6644, 0x6649, 0x6649, + 0x664B, 0x664B, 0x664F, 0x664F, 0x6652, 0x6652, 0x665D, 0x665F, 0x6662, 0x6662, 0x6664, 0x6664, 0x6666, 0x6669, 0x666E, 0x6670, + 0x6674, 0x6674, 0x6676, 0x6676, 0x667A, 0x667A, 0x6681, 0x6681, 0x6683, 0x6684, 0x6687, 0x6689, 0x668E, 0x668E, 0x6691, 0x6691, + 0x6696, 0x6698, 0x669D, 0x669D, 0x66A2, 0x66A2, 0x66A6, 0x66A6, 0x66AB, 0x66AB, 0x66AE, 0x66AE, 0x66B4, 0x66B4, 0x66B8, 0x66B9, + 0x66BC, 0x66BC, 0x66BE, 0x66BE, 0x66C1, 0x66C1, 0x66C4, 0x66C4, 0x66C7, 0x66C7, 0x66C9, 0x66C9, 0x66D6, 0x66D6, 0x66D9, 0x66DA, + 0x66DC, 0x66DD, 0x66E0, 0x66E0, 0x66E6, 0x66E6, 0x66E9, 0x66E9, 0x66F0, 0x66F0, 0x66F2, 0x66F5, 0x66F7, 0x66F9, 0x66FC, 0x6700, + 0x6703, 0x6703, 0x6708, 0x6709, 0x670B, 0x670B, 0x670D, 0x670D, 0x670F, 0x670F, 0x6714, 0x6717, 0x671B, 0x671B, 0x671D, 0x671F, + 0x6726, 0x6728, 0x672A, 0x672E, 0x6731, 0x6731, 0x6734, 0x6734, 0x6736, 0x6738, 0x673A, 0x673A, 0x673D, 0x673D, 0x673F, 0x673F, + 0x6741, 0x6741, 0x6746, 0x6746, 0x6749, 0x6749, 0x674E, 0x6751, 0x6753, 0x6753, 0x6756, 0x6756, 0x6759, 0x6759, 0x675C, 0x675C, + 0x675E, 0x6765, 0x676A, 0x676A, 0x676D, 0x676D, 0x676F, 0x6773, 0x6775, 0x6775, 0x6777, 0x6777, 0x677C, 0x677C, 0x677E, 0x677F, + 0x6785, 0x6785, 0x6787, 0x6787, 0x6789, 0x6789, 0x678B, 0x678C, 0x6790, 0x6790, 0x6795, 0x6795, 0x6797, 0x6797, 0x679A, 0x679A, + 0x679C, 0x679D, 0x67A0, 0x67A2, 0x67A6, 0x67A6, 0x67A9, 0x67A9, 0x67AF, 0x67AF, 0x67B3, 0x67B4, 0x67B6, 0x67B9, 0x67C1, 0x67C1, + 0x67C4, 0x67C4, 0x67C6, 0x67C6, 0x67CA, 0x67CA, 0x67CE, 0x67D1, 0x67D3, 0x67D4, 0x67D8, 0x67D8, 0x67DA, 0x67DA, 0x67DD, 0x67DE, + 0x67E2, 0x67E2, 0x67E4, 0x67E4, 0x67E7, 0x67E7, 0x67E9, 0x67E9, 0x67EC, 0x67EC, 0x67EE, 0x67EF, 0x67F1, 0x67F1, 0x67F3, 0x67F5, + 0x67FB, 0x67FB, 0x67FE, 0x67FF, 0x6802, 0x6804, 0x6813, 0x6813, 0x6816, 0x6817, 0x681E, 0x681E, 0x6821, 0x6822, 0x6829, 0x682B, + 0x6832, 0x6832, 0x6834, 0x6834, 0x6838, 0x6839, 0x683C, 0x683D, 0x6840, 0x6843, 0x6846, 0x6846, 0x6848, 0x6848, 0x684D, 0x684E, + 0x6850, 0x6851, 0x6853, 0x6854, 0x6859, 0x6859, 0x685C, 0x685D, 0x685F, 0x685F, 0x6863, 0x6863, 0x6867, 0x6867, 0x6874, 0x6874, + 0x6876, 0x6877, 0x687E, 0x687F, 0x6881, 0x6881, 0x6883, 0x6883, 0x6885, 0x6885, 0x688D, 0x688D, 0x688F, 0x688F, 0x6893, 0x6894, + 0x6897, 0x6897, 0x689B, 0x689B, 0x689D, 0x689D, 0x689F, 0x68A0, 0x68A2, 0x68A2, 0x68A6, 0x68A8, 0x68AD, 0x68AD, 0x68AF, 0x68B1, + 0x68B3, 0x68B3, 0x68B5, 0x68B6, 0x68B9, 0x68BA, 0x68BC, 0x68BC, 0x68C4, 0x68C4, 0x68C6, 0x68C6, 0x68C9, 0x68CB, 0x68CD, 0x68CD, + 0x68D2, 0x68D2, 0x68D4, 0x68D5, 0x68D7, 0x68D8, 0x68DA, 0x68DA, 0x68DF, 0x68E1, 0x68E3, 0x68E3, 0x68E7, 0x68E7, 0x68EE, 0x68EF, + 0x68F2, 0x68F2, 0x68F9, 0x68FA, 0x6900, 0x6901, 0x6904, 0x6905, 0x6908, 0x6908, 0x690B, 0x690F, 0x6912, 0x6912, 0x6919, 0x691C, + 0x6921, 0x6923, 0x6925, 0x6926, 0x6928, 0x6928, 0x692A, 0x692A, 0x6930, 0x6930, 0x6934, 0x6934, 0x6936, 0x6936, 0x6939, 0x6939, + 0x693D, 0x693D, 0x693F, 0x693F, 0x694A, 0x694A, 0x6953, 0x6955, 0x6959, 0x695A, 0x695C, 0x695E, 0x6960, 0x6962, 0x696A, 0x696B, + 0x696D, 0x696F, 0x6973, 0x6975, 0x6977, 0x6979, 0x697C, 0x697E, 0x6981, 0x6982, 0x698A, 0x698A, 0x698E, 0x698E, 0x6991, 0x6991, + 0x6994, 0x6995, 0x699B, 0x699C, 0x69A0, 0x69A0, 0x69A7, 0x69A7, 0x69AE, 0x69AE, 0x69B1, 0x69B2, 0x69B4, 0x69B4, 0x69BB, 0x69BB, + 0x69BE, 0x69BF, 0x69C1, 0x69C1, 0x69C3, 0x69C3, 0x69C7, 0x69C7, 0x69CA, 0x69CE, 0x69D0, 0x69D0, 0x69D3, 0x69D3, 0x69D8, 0x69D9, + 0x69DD, 0x69DE, 0x69E7, 0x69E8, 0x69EB, 0x69EB, 0x69ED, 0x69ED, 0x69F2, 0x69F2, 0x69F9, 0x69F9, 0x69FB, 0x69FB, 0x69FD, 0x69FD, + 0x69FF, 0x69FF, 0x6A02, 0x6A02, 0x6A05, 0x6A05, 0x6A0A, 0x6A0C, 0x6A12, 0x6A14, 0x6A17, 0x6A17, 0x6A19, 0x6A19, 0x6A1B, 0x6A1B, + 0x6A1E, 0x6A1F, 0x6A21, 0x6A23, 0x6A29, 0x6A2B, 0x6A2E, 0x6A2E, 0x6A35, 0x6A36, 0x6A38, 0x6A3A, 0x6A3D, 0x6A3D, 0x6A44, 0x6A44, + 0x6A47, 0x6A48, 0x6A4B, 0x6A4B, 0x6A58, 0x6A59, 0x6A5F, 0x6A5F, 0x6A61, 0x6A62, 0x6A66, 0x6A66, 0x6A72, 0x6A72, 0x6A78, 0x6A78, + 0x6A7F, 0x6A80, 0x6A84, 0x6A84, 0x6A8D, 0x6A8E, 0x6A90, 0x6A90, 0x6A97, 0x6A97, 0x6A9C, 0x6A9C, 0x6AA0, 0x6AA0, 0x6AA2, 0x6AA3, + 0x6AAA, 0x6AAA, 0x6AAC, 0x6AAC, 0x6AAE, 0x6AAE, 0x6AB3, 0x6AB3, 0x6AB8, 0x6AB8, 0x6ABB, 0x6ABB, 0x6AC1, 0x6AC3, 0x6AD1, 0x6AD1, + 0x6AD3, 0x6AD3, 0x6ADA, 0x6ADB, 0x6ADE, 0x6ADF, 0x6AE8, 0x6AE8, 0x6AEA, 0x6AEA, 0x6AFA, 0x6AFB, 0x6B04, 0x6B05, 0x6B0A, 0x6B0A, + 0x6B12, 0x6B12, 0x6B16, 0x6B16, 0x6B1D, 0x6B1D, 0x6B1F, 0x6B21, 0x6B23, 0x6B23, 0x6B27, 0x6B27, 0x6B32, 0x6B32, 0x6B37, 0x6B3A, + 0x6B3D, 0x6B3E, 0x6B43, 0x6B43, 0x6B47, 0x6B47, 0x6B49, 0x6B49, 0x6B4C, 0x6B4C, 0x6B4E, 0x6B4E, 0x6B50, 0x6B50, 0x6B53, 0x6B54, + 0x6B59, 0x6B59, 0x6B5B, 0x6B5B, 0x6B5F, 0x6B5F, 0x6B61, 0x6B64, 0x6B66, 0x6B66, 0x6B69, 0x6B6A, 0x6B6F, 0x6B6F, 0x6B73, 0x6B74, + 0x6B78, 0x6B79, 0x6B7B, 0x6B7B, 0x6B7F, 0x6B80, 0x6B83, 0x6B84, 0x6B86, 0x6B86, 0x6B89, 0x6B8B, 0x6B8D, 0x6B8D, 0x6B95, 0x6B96, + 0x6B98, 0x6B98, 0x6B9E, 0x6B9E, 0x6BA4, 0x6BA4, 0x6BAA, 0x6BAB, 0x6BAF, 0x6BAF, 0x6BB1, 0x6BB5, 0x6BB7, 0x6BB7, 0x6BBA, 0x6BBC, + 0x6BBF, 0x6BC0, 0x6BC5, 0x6BC6, 0x6BCB, 0x6BCB, 0x6BCD, 0x6BCE, 0x6BD2, 0x6BD4, 0x6BD8, 0x6BD8, 0x6BDB, 0x6BDB, 0x6BDF, 0x6BDF, + 0x6BEB, 0x6BEC, 0x6BEF, 0x6BEF, 0x6BF3, 0x6BF3, 0x6C08, 0x6C08, 0x6C0F, 0x6C0F, 0x6C11, 0x6C11, 0x6C13, 0x6C14, 0x6C17, 0x6C17, + 0x6C1B, 0x6C1B, 0x6C23, 0x6C24, 0x6C34, 0x6C34, 0x6C37, 0x6C38, 0x6C3E, 0x6C3E, 0x6C40, 0x6C42, 0x6C4E, 0x6C4E, 0x6C50, 0x6C50, + 0x6C55, 0x6C55, 0x6C57, 0x6C57, 0x6C5A, 0x6C5A, 0x6C5D, 0x6C60, 0x6C62, 0x6C62, 0x6C68, 0x6C68, 0x6C6A, 0x6C6A, 0x6C70, 0x6C70, + 0x6C72, 0x6C73, 0x6C7A, 0x6C7A, 0x6C7D, 0x6C7E, 0x6C81, 0x6C83, 0x6C88, 0x6C88, 0x6C8C, 0x6C8D, 0x6C90, 0x6C90, 0x6C92, 0x6C93, + 0x6C96, 0x6C96, 0x6C99, 0x6C9B, 0x6CA1, 0x6CA2, 0x6CAB, 0x6CAB, 0x6CAE, 0x6CAE, 0x6CB1, 0x6CB1, 0x6CB3, 0x6CB3, 0x6CB8, 0x6CBF, + 0x6CC1, 0x6CC1, 0x6CC4, 0x6CC5, 0x6CC9, 0x6CCA, 0x6CCC, 0x6CCC, 0x6CD3, 0x6CD3, 0x6CD5, 0x6CD5, 0x6CD7, 0x6CD7, 0x6CD9, 0x6CD9, + 0x6CDB, 0x6CDB, 0x6CDD, 0x6CDD, 0x6CE1, 0x6CE3, 0x6CE5, 0x6CE5, 0x6CE8, 0x6CE8, 0x6CEA, 0x6CEA, 0x6CEF, 0x6CF1, 0x6CF3, 0x6CF3, + 0x6D0B, 0x6D0C, 0x6D12, 0x6D12, 0x6D17, 0x6D17, 0x6D19, 0x6D19, 0x6D1B, 0x6D1B, 0x6D1E, 0x6D1F, 0x6D25, 0x6D25, 0x6D29, 0x6D2B, + 0x6D32, 0x6D33, 0x6D35, 0x6D36, 0x6D38, 0x6D38, 0x6D3B, 0x6D3B, 0x6D3D, 0x6D3E, 0x6D41, 0x6D41, 0x6D44, 0x6D45, 0x6D59, 0x6D5A, + 0x6D5C, 0x6D5C, 0x6D63, 0x6D64, 0x6D66, 0x6D66, 0x6D69, 0x6D6A, 0x6D6C, 0x6D6C, 0x6D6E, 0x6D6E, 0x6D74, 0x6D74, 0x6D77, 0x6D79, + 0x6D85, 0x6D85, 0x6D88, 0x6D88, 0x6D8C, 0x6D8C, 0x6D8E, 0x6D8E, 0x6D93, 0x6D93, 0x6D95, 0x6D95, 0x6D99, 0x6D99, 0x6D9B, 0x6D9C, + 0x6DAF, 0x6DAF, 0x6DB2, 0x6DB2, 0x6DB5, 0x6DB5, 0x6DB8, 0x6DB8, 0x6DBC, 0x6DBC, 0x6DC0, 0x6DC0, 0x6DC5, 0x6DC7, 0x6DCB, 0x6DCC, + 0x6DD1, 0x6DD2, 0x6DD5, 0x6DD5, 0x6DD8, 0x6DD9, 0x6DDE, 0x6DDE, 0x6DE1, 0x6DE1, 0x6DE4, 0x6DE4, 0x6DE6, 0x6DE6, 0x6DE8, 0x6DE8, + 0x6DEA, 0x6DEC, 0x6DEE, 0x6DEE, 0x6DF1, 0x6DF1, 0x6DF3, 0x6DF3, 0x6DF5, 0x6DF5, 0x6DF7, 0x6DF7, 0x6DF9, 0x6DFB, 0x6E05, 0x6E05, + 0x6E07, 0x6E0B, 0x6E13, 0x6E13, 0x6E15, 0x6E15, 0x6E19, 0x6E1B, 0x6E1D, 0x6E1D, 0x6E1F, 0x6E21, 0x6E23, 0x6E26, 0x6E29, 0x6E29, + 0x6E2B, 0x6E2F, 0x6E38, 0x6E38, 0x6E3A, 0x6E3A, 0x6E3E, 0x6E3E, 0x6E43, 0x6E43, 0x6E4A, 0x6E4A, 0x6E4D, 0x6E4E, 0x6E56, 0x6E56, + 0x6E58, 0x6E58, 0x6E5B, 0x6E5B, 0x6E5F, 0x6E5F, 0x6E67, 0x6E67, 0x6E6B, 0x6E6B, 0x6E6E, 0x6E6F, 0x6E72, 0x6E72, 0x6E76, 0x6E76, + 0x6E7E, 0x6E80, 0x6E82, 0x6E82, 0x6E8C, 0x6E8C, 0x6E8F, 0x6E90, 0x6E96, 0x6E96, 0x6E98, 0x6E98, 0x6E9C, 0x6E9D, 0x6E9F, 0x6E9F, + 0x6EA2, 0x6EA2, 0x6EA5, 0x6EA5, 0x6EAA, 0x6EAA, 0x6EAF, 0x6EAF, 0x6EB2, 0x6EB2, 0x6EB6, 0x6EB7, 0x6EBA, 0x6EBA, 0x6EBD, 0x6EBD, + 0x6EC2, 0x6EC2, 0x6EC4, 0x6EC5, 0x6EC9, 0x6EC9, 0x6ECB, 0x6ECC, 0x6ED1, 0x6ED1, 0x6ED3, 0x6ED5, 0x6EDD, 0x6EDE, 0x6EEC, 0x6EEC, + 0x6EEF, 0x6EEF, 0x6EF2, 0x6EF2, 0x6EF4, 0x6EF4, 0x6EF7, 0x6EF8, 0x6EFE, 0x6EFF, 0x6F01, 0x6F02, 0x6F06, 0x6F06, 0x6F09, 0x6F09, + 0x6F0F, 0x6F0F, 0x6F11, 0x6F11, 0x6F13, 0x6F15, 0x6F20, 0x6F20, 0x6F22, 0x6F23, 0x6F2B, 0x6F2C, 0x6F31, 0x6F32, 0x6F38, 0x6F38, + 0x6F3E, 0x6F3F, 0x6F41, 0x6F41, 0x6F45, 0x6F45, 0x6F54, 0x6F54, 0x6F58, 0x6F58, 0x6F5B, 0x6F5C, 0x6F5F, 0x6F5F, 0x6F64, 0x6F64, + 0x6F66, 0x6F66, 0x6F6D, 0x6F70, 0x6F74, 0x6F74, 0x6F78, 0x6F78, 0x6F7A, 0x6F7A, 0x6F7C, 0x6F7C, 0x6F80, 0x6F82, 0x6F84, 0x6F84, + 0x6F86, 0x6F86, 0x6F8E, 0x6F8E, 0x6F91, 0x6F91, 0x6F97, 0x6F97, 0x6FA1, 0x6FA1, 0x6FA3, 0x6FA4, 0x6FAA, 0x6FAA, 0x6FB1, 0x6FB1, + 0x6FB3, 0x6FB3, 0x6FB9, 0x6FB9, 0x6FC0, 0x6FC3, 0x6FC6, 0x6FC6, 0x6FD4, 0x6FD5, 0x6FD8, 0x6FD8, 0x6FDB, 0x6FDB, 0x6FDF, 0x6FE1, + 0x6FE4, 0x6FE4, 0x6FEB, 0x6FEC, 0x6FEE, 0x6FEF, 0x6FF1, 0x6FF1, 0x6FF3, 0x6FF3, 0x6FF6, 0x6FF6, 0x6FFA, 0x6FFA, 0x6FFE, 0x6FFE, + 0x7001, 0x7001, 0x7009, 0x7009, 0x700B, 0x700B, 0x700F, 0x700F, 0x7011, 0x7011, 0x7015, 0x7015, 0x7018, 0x7018, 0x701A, 0x701B, + 0x701D, 0x701F, 0x7026, 0x7027, 0x702C, 0x702C, 0x7030, 0x7030, 0x7032, 0x7032, 0x703E, 0x703E, 0x704C, 0x704C, 0x7051, 0x7051, + 0x7058, 0x7058, 0x7063, 0x7063, 0x706B, 0x706B, 0x706F, 0x7070, 0x7078, 0x7078, 0x707C, 0x707D, 0x7089, 0x708A, 0x708E, 0x708E, + 0x7092, 0x7092, 0x7099, 0x7099, 0x70AC, 0x70AF, 0x70B3, 0x70B3, 0x70B8, 0x70BA, 0x70C8, 0x70C8, 0x70CB, 0x70CB, 0x70CF, 0x70CF, + 0x70D9, 0x70D9, 0x70DD, 0x70DD, 0x70DF, 0x70DF, 0x70F1, 0x70F1, 0x70F9, 0x70F9, 0x70FD, 0x70FD, 0x7109, 0x7109, 0x7114, 0x7114, + 0x7119, 0x711A, 0x711C, 0x711C, 0x7121, 0x7121, 0x7126, 0x7126, 0x7136, 0x7136, 0x713C, 0x713C, 0x7149, 0x7149, 0x714C, 0x714C, + 0x714E, 0x714E, 0x7155, 0x7156, 0x7159, 0x7159, 0x7162, 0x7162, 0x7164, 0x7167, 0x7169, 0x7169, 0x716C, 0x716C, 0x716E, 0x716E, + 0x717D, 0x717D, 0x7184, 0x7184, 0x7188, 0x7188, 0x718A, 0x718A, 0x718F, 0x718F, 0x7194, 0x7195, 0x7199, 0x7199, 0x719F, 0x719F, + 0x71A8, 0x71A8, 0x71AC, 0x71AC, 0x71B1, 0x71B1, 0x71B9, 0x71B9, 0x71BE, 0x71BE, 0x71C3, 0x71C3, 0x71C8, 0x71C9, 0x71CE, 0x71CE, + 0x71D0, 0x71D0, 0x71D2, 0x71D2, 0x71D4, 0x71D5, 0x71D7, 0x71D7, 0x71DF, 0x71E0, 0x71E5, 0x71E7, 0x71EC, 0x71EE, 0x71F5, 0x71F5, + 0x71F9, 0x71F9, 0x71FB, 0x71FC, 0x71FF, 0x71FF, 0x7206, 0x7206, 0x720D, 0x720D, 0x7210, 0x7210, 0x721B, 0x721B, 0x7228, 0x7228, + 0x722A, 0x722A, 0x722C, 0x722D, 0x7230, 0x7230, 0x7232, 0x7232, 0x7235, 0x7236, 0x723A, 0x7240, 0x7246, 0x7248, 0x724B, 0x724C, + 0x7252, 0x7252, 0x7258, 0x7259, 0x725B, 0x725B, 0x725D, 0x725D, 0x725F, 0x725F, 0x7261, 0x7262, 0x7267, 0x7267, 0x7269, 0x7269, + 0x7272, 0x7272, 0x7274, 0x7274, 0x7279, 0x7279, 0x727D, 0x727E, 0x7280, 0x7282, 0x7287, 0x7287, 0x7292, 0x7292, 0x7296, 0x7296, + 0x72A0, 0x72A0, 0x72A2, 0x72A2, 0x72A7, 0x72A7, 0x72AC, 0x72AC, 0x72AF, 0x72AF, 0x72B2, 0x72B2, 0x72B6, 0x72B6, 0x72B9, 0x72B9, + 0x72C2, 0x72C4, 0x72C6, 0x72C6, 0x72CE, 0x72CE, 0x72D0, 0x72D0, 0x72D2, 0x72D2, 0x72D7, 0x72D7, 0x72D9, 0x72D9, 0x72DB, 0x72DB, + 0x72E0, 0x72E2, 0x72E9, 0x72E9, 0x72EC, 0x72ED, 0x72F7, 0x72F9, 0x72FC, 0x72FD, 0x730A, 0x730A, 0x7316, 0x7317, 0x731B, 0x731D, + 0x731F, 0x731F, 0x7325, 0x7325, 0x7329, 0x732B, 0x732E, 0x732F, 0x7334, 0x7334, 0x7336, 0x7337, 0x733E, 0x733F, 0x7344, 0x7345, + 0x734E, 0x734F, 0x7357, 0x7357, 0x7363, 0x7363, 0x7368, 0x7368, 0x736A, 0x736A, 0x7370, 0x7370, 0x7372, 0x7372, 0x7375, 0x7375, + 0x7378, 0x7378, 0x737A, 0x737B, 0x7384, 0x7384, 0x7387, 0x7387, 0x7389, 0x7389, 0x738B, 0x738B, 0x7396, 0x7396, 0x73A9, 0x73A9, + 0x73B2, 0x73B3, 0x73BB, 0x73BB, 0x73C0, 0x73C0, 0x73C2, 0x73C2, 0x73C8, 0x73C8, 0x73CA, 0x73CA, 0x73CD, 0x73CE, 0x73DE, 0x73DE, + 0x73E0, 0x73E0, 0x73E5, 0x73E5, 0x73EA, 0x73EA, 0x73ED, 0x73EE, 0x73F1, 0x73F1, 0x73F8, 0x73F8, 0x73FE, 0x73FE, 0x7403, 0x7403, + 0x7405, 0x7406, 0x7409, 0x7409, 0x7422, 0x7422, 0x7425, 0x7425, 0x7432, 0x7436, 0x743A, 0x743A, 0x743F, 0x743F, 0x7441, 0x7441, + 0x7455, 0x7455, 0x7459, 0x745C, 0x745E, 0x7460, 0x7463, 0x7464, 0x7469, 0x746A, 0x746F, 0x7470, 0x7473, 0x7473, 0x7476, 0x7476, + 0x747E, 0x747E, 0x7483, 0x7483, 0x748B, 0x748B, 0x749E, 0x749E, 0x74A2, 0x74A2, 0x74A7, 0x74A7, 0x74B0, 0x74B0, 0x74BD, 0x74BD, + 0x74CA, 0x74CA, 0x74CF, 0x74CF, 0x74D4, 0x74D4, 0x74DC, 0x74DC, 0x74E0, 0x74E0, 0x74E2, 0x74E3, 0x74E6, 0x74E7, 0x74E9, 0x74E9, + 0x74EE, 0x74EE, 0x74F0, 0x74F2, 0x74F6, 0x74F8, 0x7503, 0x7505, 0x750C, 0x750E, 0x7511, 0x7511, 0x7513, 0x7513, 0x7515, 0x7515, + 0x7518, 0x7518, 0x751A, 0x751A, 0x751C, 0x751C, 0x751E, 0x751F, 0x7523, 0x7523, 0x7525, 0x7526, 0x7528, 0x7528, 0x752B, 0x752C, + 0x7530, 0x7533, 0x7537, 0x7538, 0x753A, 0x753C, 0x7544, 0x7544, 0x7546, 0x7546, 0x7549, 0x754D, 0x754F, 0x754F, 0x7551, 0x7551, + 0x7554, 0x7554, 0x7559, 0x755D, 0x7560, 0x7560, 0x7562, 0x7562, 0x7564, 0x7567, 0x7569, 0x756B, 0x756D, 0x756D, 0x7570, 0x7570, + 0x7573, 0x7574, 0x7576, 0x7578, 0x757F, 0x757F, 0x7582, 0x7582, 0x7586, 0x7587, 0x7589, 0x758B, 0x758E, 0x758F, 0x7591, 0x7591, + 0x7594, 0x7594, 0x759A, 0x759A, 0x759D, 0x759D, 0x75A3, 0x75A3, 0x75A5, 0x75A5, 0x75AB, 0x75AB, 0x75B1, 0x75B3, 0x75B5, 0x75B5, + 0x75B8, 0x75B9, 0x75BC, 0x75BE, 0x75C2, 0x75C3, 0x75C5, 0x75C5, 0x75C7, 0x75C7, 0x75CA, 0x75CA, 0x75CD, 0x75CD, 0x75D2, 0x75D2, + 0x75D4, 0x75D5, 0x75D8, 0x75D9, 0x75DB, 0x75DB, 0x75DE, 0x75DE, 0x75E2, 0x75E3, 0x75E9, 0x75E9, 0x75F0, 0x75F0, 0x75F2, 0x75F4, + 0x75FA, 0x75FA, 0x75FC, 0x75FC, 0x75FE, 0x75FF, 0x7601, 0x7601, 0x7609, 0x7609, 0x760B, 0x760B, 0x760D, 0x760D, 0x761F, 0x7622, + 0x7624, 0x7624, 0x7627, 0x7627, 0x7630, 0x7630, 0x7634, 0x7634, 0x763B, 0x763B, 0x7642, 0x7642, 0x7646, 0x7648, 0x764C, 0x764C, + 0x7652, 0x7652, 0x7656, 0x7656, 0x7658, 0x7658, 0x765C, 0x765C, 0x7661, 0x7662, 0x7667, 0x766A, 0x766C, 0x766C, 0x7670, 0x7670, + 0x7672, 0x7672, 0x7676, 0x7676, 0x7678, 0x7678, 0x767A, 0x767E, 0x7680, 0x7680, 0x7683, 0x7684, 0x7686, 0x7688, 0x768B, 0x768B, + 0x768E, 0x768E, 0x7690, 0x7690, 0x7693, 0x7693, 0x7696, 0x7696, 0x7699, 0x769A, 0x76AE, 0x76AE, 0x76B0, 0x76B0, 0x76B4, 0x76B4, + 0x76B7, 0x76BA, 0x76BF, 0x76BF, 0x76C2, 0x76C3, 0x76C6, 0x76C6, 0x76C8, 0x76C8, 0x76CA, 0x76CA, 0x76CD, 0x76CD, 0x76D2, 0x76D2, + 0x76D6, 0x76D7, 0x76DB, 0x76DC, 0x76DE, 0x76DF, 0x76E1, 0x76E1, 0x76E3, 0x76E5, 0x76E7, 0x76E7, 0x76EA, 0x76EA, 0x76EE, 0x76EE, + 0x76F2, 0x76F2, 0x76F4, 0x76F4, 0x76F8, 0x76F8, 0x76FB, 0x76FB, 0x76FE, 0x76FE, 0x7701, 0x7701, 0x7704, 0x7704, 0x7707, 0x7709, + 0x770B, 0x770C, 0x771B, 0x771B, 0x771E, 0x7720, 0x7724, 0x7726, 0x7729, 0x7729, 0x7737, 0x7738, 0x773A, 0x773A, 0x773C, 0x773C, + 0x7740, 0x7740, 0x7747, 0x7747, 0x775A, 0x775B, 0x7761, 0x7761, 0x7763, 0x7763, 0x7765, 0x7766, 0x7768, 0x7768, 0x776B, 0x776B, + 0x7779, 0x7779, 0x777E, 0x777F, 0x778B, 0x778B, 0x778E, 0x778E, 0x7791, 0x7791, 0x779E, 0x779E, 0x77A0, 0x77A0, 0x77A5, 0x77A5, + 0x77AC, 0x77AD, 0x77B0, 0x77B0, 0x77B3, 0x77B3, 0x77B6, 0x77B6, 0x77B9, 0x77B9, 0x77BB, 0x77BD, 0x77BF, 0x77BF, 0x77C7, 0x77C7, + 0x77CD, 0x77CD, 0x77D7, 0x77D7, 0x77DA, 0x77DC, 0x77E2, 0x77E3, 0x77E5, 0x77E5, 0x77E7, 0x77E7, 0x77E9, 0x77E9, 0x77ED, 0x77EF, + 0x77F3, 0x77F3, 0x77FC, 0x77FC, 0x7802, 0x7802, 0x780C, 0x780C, 0x7812, 0x7812, 0x7814, 0x7815, 0x7820, 0x7820, 0x7825, 0x7827, + 0x7832, 0x7832, 0x7834, 0x7834, 0x783A, 0x783A, 0x783F, 0x783F, 0x7845, 0x7845, 0x785D, 0x785D, 0x786B, 0x786C, 0x786F, 0x786F, + 0x7872, 0x7872, 0x7874, 0x7874, 0x787C, 0x787C, 0x7881, 0x7881, 0x7886, 0x7887, 0x788C, 0x788E, 0x7891, 0x7891, 0x7893, 0x7893, + 0x7895, 0x7895, 0x7897, 0x7897, 0x789A, 0x789A, 0x78A3, 0x78A3, 0x78A7, 0x78A7, 0x78A9, 0x78AA, 0x78AF, 0x78AF, 0x78B5, 0x78B5, + 0x78BA, 0x78BA, 0x78BC, 0x78BC, 0x78BE, 0x78BE, 0x78C1, 0x78C1, 0x78C5, 0x78C6, 0x78CA, 0x78CB, 0x78D0, 0x78D1, 0x78D4, 0x78D4, + 0x78DA, 0x78DA, 0x78E7, 0x78E8, 0x78EC, 0x78EC, 0x78EF, 0x78EF, 0x78F4, 0x78F4, 0x78FD, 0x78FD, 0x7901, 0x7901, 0x7907, 0x7907, + 0x790E, 0x790E, 0x7911, 0x7912, 0x7919, 0x7919, 0x7926, 0x7926, 0x792A, 0x792C, 0x793A, 0x793A, 0x793C, 0x793C, 0x793E, 0x793E, + 0x7940, 0x7941, 0x7947, 0x7949, 0x7950, 0x7950, 0x7953, 0x7953, 0x7955, 0x7957, 0x795A, 0x795A, 0x795D, 0x7960, 0x7962, 0x7962, + 0x7965, 0x7965, 0x7968, 0x7968, 0x796D, 0x796D, 0x7977, 0x7977, 0x797A, 0x797A, 0x797F, 0x7981, 0x7984, 0x7985, 0x798A, 0x798A, + 0x798D, 0x798F, 0x799D, 0x799D, 0x79A6, 0x79A7, 0x79AA, 0x79AA, 0x79AE, 0x79AE, 0x79B0, 0x79B0, 0x79B3, 0x79B3, 0x79B9, 0x79BA, + 0x79BD, 0x79C1, 0x79C9, 0x79C9, 0x79CB, 0x79CB, 0x79D1, 0x79D2, 0x79D5, 0x79D5, 0x79D8, 0x79D8, 0x79DF, 0x79DF, 0x79E1, 0x79E1, + 0x79E3, 0x79E4, 0x79E6, 0x79E7, 0x79E9, 0x79E9, 0x79EC, 0x79EC, 0x79F0, 0x79F0, 0x79FB, 0x79FB, 0x7A00, 0x7A00, 0x7A08, 0x7A08, + 0x7A0B, 0x7A0B, 0x7A0D, 0x7A0E, 0x7A14, 0x7A14, 0x7A17, 0x7A1A, 0x7A1C, 0x7A1C, 0x7A1F, 0x7A20, 0x7A2E, 0x7A2E, 0x7A31, 0x7A32, + 0x7A37, 0x7A37, 0x7A3B, 0x7A40, 0x7A42, 0x7A43, 0x7A46, 0x7A46, 0x7A49, 0x7A49, 0x7A4D, 0x7A50, 0x7A57, 0x7A57, 0x7A61, 0x7A63, + 0x7A69, 0x7A69, 0x7A6B, 0x7A6B, 0x7A70, 0x7A70, 0x7A74, 0x7A74, 0x7A76, 0x7A76, 0x7A79, 0x7A7A, 0x7A7D, 0x7A7D, 0x7A7F, 0x7A7F, + 0x7A81, 0x7A81, 0x7A83, 0x7A84, 0x7A88, 0x7A88, 0x7A92, 0x7A93, 0x7A95, 0x7A98, 0x7A9F, 0x7A9F, 0x7AA9, 0x7AAA, 0x7AAE, 0x7AB0, + 0x7AB6, 0x7AB6, 0x7ABA, 0x7ABA, 0x7ABF, 0x7ABF, 0x7AC3, 0x7AC5, 0x7AC7, 0x7AC8, 0x7ACA, 0x7ACB, 0x7ACD, 0x7ACD, 0x7ACF, 0x7ACF, + 0x7AD2, 0x7AD3, 0x7AD5, 0x7AD5, 0x7AD9, 0x7ADA, 0x7ADC, 0x7ADD, 0x7ADF, 0x7AE3, 0x7AE5, 0x7AE6, 0x7AEA, 0x7AEA, 0x7AED, 0x7AED, + 0x7AEF, 0x7AF0, 0x7AF6, 0x7AF6, 0x7AF8, 0x7AFA, 0x7AFF, 0x7AFF, 0x7B02, 0x7B02, 0x7B04, 0x7B04, 0x7B06, 0x7B06, 0x7B08, 0x7B08, + 0x7B0A, 0x7B0B, 0x7B0F, 0x7B0F, 0x7B11, 0x7B11, 0x7B18, 0x7B19, 0x7B1B, 0x7B1B, 0x7B1E, 0x7B1E, 0x7B20, 0x7B20, 0x7B25, 0x7B26, + 0x7B28, 0x7B28, 0x7B2C, 0x7B2C, 0x7B33, 0x7B33, 0x7B35, 0x7B36, 0x7B39, 0x7B39, 0x7B45, 0x7B46, 0x7B48, 0x7B49, 0x7B4B, 0x7B4D, + 0x7B4F, 0x7B52, 0x7B54, 0x7B54, 0x7B56, 0x7B56, 0x7B5D, 0x7B5D, 0x7B65, 0x7B65, 0x7B67, 0x7B67, 0x7B6C, 0x7B6C, 0x7B6E, 0x7B6E, + 0x7B70, 0x7B71, 0x7B74, 0x7B75, 0x7B7A, 0x7B7A, 0x7B86, 0x7B87, 0x7B8B, 0x7B8B, 0x7B8D, 0x7B8D, 0x7B8F, 0x7B8F, 0x7B92, 0x7B92, + 0x7B94, 0x7B95, 0x7B97, 0x7B9A, 0x7B9C, 0x7B9D, 0x7B9F, 0x7B9F, 0x7BA1, 0x7BA1, 0x7BAA, 0x7BAA, 0x7BAD, 0x7BAD, 0x7BB1, 0x7BB1, + 0x7BB4, 0x7BB4, 0x7BB8, 0x7BB8, 0x7BC0, 0x7BC1, 0x7BC4, 0x7BC4, 0x7BC6, 0x7BC7, 0x7BC9, 0x7BC9, 0x7BCB, 0x7BCC, 0x7BCF, 0x7BCF, + 0x7BDD, 0x7BDD, 0x7BE0, 0x7BE0, 0x7BE4, 0x7BE6, 0x7BE9, 0x7BE9, 0x7BED, 0x7BED, 0x7BF3, 0x7BF3, 0x7BF6, 0x7BF7, 0x7C00, 0x7C00, + 0x7C07, 0x7C07, 0x7C0D, 0x7C0D, 0x7C11, 0x7C14, 0x7C17, 0x7C17, 0x7C1F, 0x7C1F, 0x7C21, 0x7C21, 0x7C23, 0x7C23, 0x7C27, 0x7C27, + 0x7C2A, 0x7C2B, 0x7C37, 0x7C38, 0x7C3D, 0x7C40, 0x7C43, 0x7C43, 0x7C4C, 0x7C4D, 0x7C4F, 0x7C50, 0x7C54, 0x7C54, 0x7C56, 0x7C56, + 0x7C58, 0x7C58, 0x7C5F, 0x7C60, 0x7C64, 0x7C65, 0x7C6C, 0x7C6C, 0x7C73, 0x7C73, 0x7C75, 0x7C75, 0x7C7E, 0x7C7E, 0x7C81, 0x7C83, + 0x7C89, 0x7C89, 0x7C8B, 0x7C8B, 0x7C8D, 0x7C8D, 0x7C90, 0x7C90, 0x7C92, 0x7C92, 0x7C95, 0x7C95, 0x7C97, 0x7C98, 0x7C9B, 0x7C9B, + 0x7C9F, 0x7C9F, 0x7CA1, 0x7CA2, 0x7CA4, 0x7CA5, 0x7CA7, 0x7CA8, 0x7CAB, 0x7CAB, 0x7CAD, 0x7CAE, 0x7CB1, 0x7CB3, 0x7CB9, 0x7CB9, + 0x7CBD, 0x7CBE, 0x7CC0, 0x7CC0, 0x7CC2, 0x7CC2, 0x7CC5, 0x7CC5, 0x7CCA, 0x7CCA, 0x7CCE, 0x7CCE, 0x7CD2, 0x7CD2, 0x7CD6, 0x7CD6, + 0x7CD8, 0x7CD8, 0x7CDC, 0x7CDC, 0x7CDE, 0x7CE0, 0x7CE2, 0x7CE2, 0x7CE7, 0x7CE7, 0x7CEF, 0x7CEF, 0x7CF2, 0x7CF2, 0x7CF4, 0x7CF4, + 0x7CF6, 0x7CF6, 0x7CF8, 0x7CF8, 0x7CFA, 0x7CFB, 0x7CFE, 0x7CFE, 0x7D00, 0x7D00, 0x7D02, 0x7D02, 0x7D04, 0x7D06, 0x7D0A, 0x7D0B, + 0x7D0D, 0x7D0D, 0x7D10, 0x7D10, 0x7D14, 0x7D15, 0x7D17, 0x7D1C, 0x7D20, 0x7D22, 0x7D2B, 0x7D2C, 0x7D2E, 0x7D30, 0x7D32, 0x7D33, + 0x7D35, 0x7D35, 0x7D39, 0x7D3A, 0x7D3F, 0x7D3F, 0x7D42, 0x7D46, 0x7D4B, 0x7D4C, 0x7D4E, 0x7D50, 0x7D56, 0x7D56, 0x7D5B, 0x7D5B, + 0x7D5E, 0x7D5E, 0x7D61, 0x7D63, 0x7D66, 0x7D66, 0x7D68, 0x7D68, 0x7D6E, 0x7D6E, 0x7D71, 0x7D73, 0x7D75, 0x7D76, 0x7D79, 0x7D79, + 0x7D7D, 0x7D7D, 0x7D89, 0x7D89, 0x7D8F, 0x7D8F, 0x7D93, 0x7D93, 0x7D99, 0x7D9C, 0x7D9F, 0x7D9F, 0x7DA2, 0x7DA3, 0x7DAB, 0x7DB2, + 0x7DB4, 0x7DB5, 0x7DB8, 0x7DB8, 0x7DBA, 0x7DBB, 0x7DBD, 0x7DBF, 0x7DC7, 0x7DC7, 0x7DCA, 0x7DCB, 0x7DCF, 0x7DCF, 0x7DD1, 0x7DD2, + 0x7DD5, 0x7DD5, 0x7DD8, 0x7DD8, 0x7DDA, 0x7DDA, 0x7DDC, 0x7DDE, 0x7DE0, 0x7DE1, 0x7DE4, 0x7DE4, 0x7DE8, 0x7DE9, 0x7DEC, 0x7DEC, + 0x7DEF, 0x7DEF, 0x7DF2, 0x7DF2, 0x7DF4, 0x7DF4, 0x7DFB, 0x7DFB, 0x7E01, 0x7E01, 0x7E04, 0x7E05, 0x7E09, 0x7E0B, 0x7E12, 0x7E12, + 0x7E1B, 0x7E1B, 0x7E1E, 0x7E1F, 0x7E21, 0x7E23, 0x7E26, 0x7E26, 0x7E2B, 0x7E2B, 0x7E2E, 0x7E2E, 0x7E31, 0x7E32, 0x7E35, 0x7E35, + 0x7E37, 0x7E37, 0x7E39, 0x7E3B, 0x7E3D, 0x7E3E, 0x7E41, 0x7E41, 0x7E43, 0x7E43, 0x7E46, 0x7E46, 0x7E4A, 0x7E4B, 0x7E4D, 0x7E4D, + 0x7E54, 0x7E56, 0x7E59, 0x7E5A, 0x7E5D, 0x7E5E, 0x7E66, 0x7E67, 0x7E69, 0x7E6A, 0x7E6D, 0x7E6D, 0x7E70, 0x7E70, 0x7E79, 0x7E79, + 0x7E7B, 0x7E7D, 0x7E7F, 0x7E7F, 0x7E82, 0x7E83, 0x7E88, 0x7E89, 0x7E8C, 0x7E8C, 0x7E8E, 0x7E90, 0x7E92, 0x7E94, 0x7E96, 0x7E96, + 0x7E9B, 0x7E9C, 0x7F36, 0x7F36, 0x7F38, 0x7F38, 0x7F3A, 0x7F3A, 0x7F45, 0x7F45, 0x7F4C, 0x7F4E, 0x7F50, 0x7F51, 0x7F54, 0x7F55, + 0x7F58, 0x7F58, 0x7F5F, 0x7F60, 0x7F67, 0x7F6B, 0x7F6E, 0x7F6E, 0x7F70, 0x7F70, 0x7F72, 0x7F72, 0x7F75, 0x7F75, 0x7F77, 0x7F79, + 0x7F82, 0x7F83, 0x7F85, 0x7F88, 0x7F8A, 0x7F8A, 0x7F8C, 0x7F8C, 0x7F8E, 0x7F8E, 0x7F94, 0x7F94, 0x7F9A, 0x7F9A, 0x7F9D, 0x7F9E, + 0x7FA3, 0x7FA4, 0x7FA8, 0x7FA9, 0x7FAE, 0x7FAF, 0x7FB2, 0x7FB2, 0x7FB6, 0x7FB6, 0x7FB8, 0x7FB9, 0x7FBD, 0x7FBD, 0x7FC1, 0x7FC1, + 0x7FC5, 0x7FC6, 0x7FCA, 0x7FCA, 0x7FCC, 0x7FCC, 0x7FD2, 0x7FD2, 0x7FD4, 0x7FD5, 0x7FE0, 0x7FE1, 0x7FE6, 0x7FE6, 0x7FE9, 0x7FE9, + 0x7FEB, 0x7FEB, 0x7FF0, 0x7FF0, 0x7FF3, 0x7FF3, 0x7FF9, 0x7FF9, 0x7FFB, 0x7FFC, 0x8000, 0x8001, 0x8003, 0x8006, 0x800B, 0x800C, + 0x8010, 0x8010, 0x8012, 0x8012, 0x8015, 0x8015, 0x8017, 0x8019, 0x801C, 0x801C, 0x8021, 0x8021, 0x8028, 0x8028, 0x8033, 0x8033, + 0x8036, 0x8036, 0x803B, 0x803B, 0x803D, 0x803D, 0x803F, 0x803F, 0x8046, 0x8046, 0x804A, 0x804A, 0x8052, 0x8052, 0x8056, 0x8056, + 0x8058, 0x8058, 0x805A, 0x805A, 0x805E, 0x805F, 0x8061, 0x8062, 0x8068, 0x8068, 0x806F, 0x8070, 0x8072, 0x8074, 0x8076, 0x8077, + 0x8079, 0x8079, 0x807D, 0x807F, 0x8084, 0x8087, 0x8089, 0x8089, 0x808B, 0x808C, 0x8093, 0x8093, 0x8096, 0x8096, 0x8098, 0x8098, + 0x809A, 0x809B, 0x809D, 0x809D, 0x80A1, 0x80A2, 0x80A5, 0x80A5, 0x80A9, 0x80AA, 0x80AC, 0x80AD, 0x80AF, 0x80AF, 0x80B1, 0x80B2, + 0x80B4, 0x80B4, 0x80BA, 0x80BA, 0x80C3, 0x80C4, 0x80C6, 0x80C6, 0x80CC, 0x80CC, 0x80CE, 0x80CE, 0x80D6, 0x80D6, 0x80D9, 0x80DB, + 0x80DD, 0x80DE, 0x80E1, 0x80E1, 0x80E4, 0x80E5, 0x80EF, 0x80EF, 0x80F1, 0x80F1, 0x80F4, 0x80F4, 0x80F8, 0x80F8, 0x80FC, 0x80FD, + 0x8102, 0x8102, 0x8105, 0x810A, 0x811A, 0x811B, 0x8123, 0x8123, 0x8129, 0x8129, 0x812F, 0x812F, 0x8131, 0x8131, 0x8133, 0x8133, + 0x8139, 0x8139, 0x813E, 0x813E, 0x8146, 0x8146, 0x814B, 0x814B, 0x814E, 0x814E, 0x8150, 0x8151, 0x8153, 0x8155, 0x815F, 0x815F, + 0x8165, 0x8166, 0x816B, 0x816B, 0x816E, 0x816E, 0x8170, 0x8171, 0x8174, 0x8174, 0x8178, 0x817A, 0x817F, 0x8180, 0x8182, 0x8183, + 0x8188, 0x8188, 0x818A, 0x818A, 0x818F, 0x818F, 0x8193, 0x8193, 0x8195, 0x8195, 0x819A, 0x819A, 0x819C, 0x819D, 0x81A0, 0x81A0, + 0x81A3, 0x81A4, 0x81A8, 0x81A9, 0x81B0, 0x81B0, 0x81B3, 0x81B3, 0x81B5, 0x81B5, 0x81B8, 0x81B8, 0x81BA, 0x81BA, 0x81BD, 0x81C0, + 0x81C2, 0x81C2, 0x81C6, 0x81C6, 0x81C8, 0x81C9, 0x81CD, 0x81CD, 0x81D1, 0x81D1, 0x81D3, 0x81D3, 0x81D8, 0x81DA, 0x81DF, 0x81E0, + 0x81E3, 0x81E3, 0x81E5, 0x81E5, 0x81E7, 0x81E8, 0x81EA, 0x81EA, 0x81ED, 0x81ED, 0x81F3, 0x81F4, 0x81FA, 0x81FC, 0x81FE, 0x81FE, + 0x8201, 0x8202, 0x8205, 0x8205, 0x8207, 0x820A, 0x820C, 0x820E, 0x8210, 0x8210, 0x8212, 0x8212, 0x8216, 0x8218, 0x821B, 0x821C, + 0x821E, 0x821F, 0x8229, 0x822C, 0x822E, 0x822E, 0x8233, 0x8233, 0x8235, 0x8239, 0x8240, 0x8240, 0x8247, 0x8247, 0x8258, 0x825A, + 0x825D, 0x825D, 0x825F, 0x825F, 0x8262, 0x8262, 0x8264, 0x8264, 0x8266, 0x8266, 0x8268, 0x8268, 0x826A, 0x826B, 0x826E, 0x826F, + 0x8271, 0x8272, 0x8276, 0x8278, 0x827E, 0x827E, 0x828B, 0x828B, 0x828D, 0x828D, 0x8292, 0x8292, 0x8299, 0x8299, 0x829D, 0x829D, + 0x829F, 0x829F, 0x82A5, 0x82A6, 0x82AB, 0x82AD, 0x82AF, 0x82AF, 0x82B1, 0x82B1, 0x82B3, 0x82B3, 0x82B8, 0x82B9, 0x82BB, 0x82BB, + 0x82BD, 0x82BD, 0x82C5, 0x82C5, 0x82D1, 0x82D4, 0x82D7, 0x82D7, 0x82D9, 0x82D9, 0x82DB, 0x82DC, 0x82DE, 0x82DF, 0x82E1, 0x82E1, + 0x82E3, 0x82E3, 0x82E5, 0x82E7, 0x82EB, 0x82EB, 0x82F1, 0x82F1, 0x82F3, 0x82F4, 0x82F9, 0x82FB, 0x8302, 0x8306, 0x8309, 0x8309, + 0x830E, 0x830E, 0x8316, 0x8318, 0x831C, 0x831C, 0x8323, 0x8323, 0x8328, 0x8328, 0x832B, 0x832B, 0x832F, 0x832F, 0x8331, 0x8332, + 0x8334, 0x8336, 0x8338, 0x8339, 0x8340, 0x8340, 0x8345, 0x8345, 0x8349, 0x834A, 0x834F, 0x8350, 0x8352, 0x8352, 0x8358, 0x8358, + 0x8373, 0x8373, 0x8375, 0x8375, 0x8377, 0x8377, 0x837B, 0x837C, 0x8385, 0x8385, 0x8387, 0x8387, 0x8389, 0x838A, 0x838E, 0x838E, + 0x8393, 0x8393, 0x8396, 0x8396, 0x839A, 0x839A, 0x839E, 0x83A0, 0x83A2, 0x83A2, 0x83A8, 0x83A8, 0x83AA, 0x83AB, 0x83B1, 0x83B1, + 0x83B5, 0x83B5, 0x83BD, 0x83BD, 0x83C1, 0x83C1, 0x83C5, 0x83C5, 0x83CA, 0x83CA, 0x83CC, 0x83CC, 0x83CE, 0x83CE, 0x83D3, 0x83D3, + 0x83D6, 0x83D6, 0x83D8, 0x83D8, 0x83DC, 0x83DC, 0x83DF, 0x83E0, 0x83E9, 0x83E9, 0x83EB, 0x83EB, 0x83EF, 0x83F2, 0x83F4, 0x83F4, + 0x83F7, 0x83F7, 0x83FB, 0x83FB, 0x83FD, 0x83FD, 0x8403, 0x8404, 0x8407, 0x8407, 0x840B, 0x840E, 0x8413, 0x8413, 0x8420, 0x8420, + 0x8422, 0x8422, 0x8429, 0x842A, 0x842C, 0x842C, 0x8431, 0x8431, 0x8435, 0x8435, 0x8438, 0x8438, 0x843C, 0x843D, 0x8446, 0x8446, + 0x8449, 0x8449, 0x844E, 0x844E, 0x8457, 0x8457, 0x845B, 0x845B, 0x8461, 0x8463, 0x8466, 0x8466, 0x8469, 0x8469, 0x846B, 0x846F, + 0x8471, 0x8471, 0x8475, 0x8475, 0x8477, 0x8477, 0x8479, 0x847A, 0x8482, 0x8482, 0x8484, 0x8484, 0x848B, 0x848B, 0x8490, 0x8490, + 0x8494, 0x8494, 0x8499, 0x8499, 0x849C, 0x849C, 0x849F, 0x849F, 0x84A1, 0x84A1, 0x84AD, 0x84AD, 0x84B2, 0x84B2, 0x84B8, 0x84B9, + 0x84BB, 0x84BC, 0x84BF, 0x84BF, 0x84C1, 0x84C1, 0x84C4, 0x84C4, 0x84C6, 0x84C6, 0x84C9, 0x84CB, 0x84CD, 0x84CD, 0x84D0, 0x84D1, + 0x84D6, 0x84D6, 0x84D9, 0x84DA, 0x84EC, 0x84EC, 0x84EE, 0x84EE, 0x84F4, 0x84F4, 0x84FC, 0x84FC, 0x84FF, 0x8500, 0x8506, 0x8506, + 0x8511, 0x8511, 0x8513, 0x8515, 0x8517, 0x8518, 0x851A, 0x851A, 0x851F, 0x851F, 0x8521, 0x8521, 0x8526, 0x8526, 0x852C, 0x852D, + 0x8535, 0x8535, 0x853D, 0x853D, 0x8540, 0x8541, 0x8543, 0x8543, 0x8548, 0x854B, 0x854E, 0x854E, 0x8555, 0x8555, 0x8557, 0x8558, + 0x855A, 0x855A, 0x8563, 0x8563, 0x8568, 0x856A, 0x856D, 0x856D, 0x8577, 0x8577, 0x857E, 0x857E, 0x8580, 0x8580, 0x8584, 0x8584, + 0x8587, 0x8588, 0x858A, 0x858A, 0x8590, 0x8591, 0x8594, 0x8594, 0x8597, 0x8597, 0x8599, 0x8599, 0x859B, 0x859C, 0x85A4, 0x85A4, + 0x85A6, 0x85A6, 0x85A8, 0x85AC, 0x85AE, 0x85AF, 0x85B9, 0x85BA, 0x85C1, 0x85C1, 0x85C9, 0x85C9, 0x85CD, 0x85CD, 0x85CF, 0x85D0, + 0x85D5, 0x85D5, 0x85DC, 0x85DD, 0x85E4, 0x85E5, 0x85E9, 0x85EA, 0x85F7, 0x85F7, 0x85F9, 0x85FB, 0x85FE, 0x85FE, 0x8602, 0x8602, + 0x8606, 0x8607, 0x860A, 0x860B, 0x8613, 0x8613, 0x8616, 0x8617, 0x861A, 0x861A, 0x8622, 0x8622, 0x862D, 0x862D, 0x862F, 0x8630, + 0x863F, 0x863F, 0x864D, 0x864E, 0x8650, 0x8650, 0x8654, 0x8655, 0x865A, 0x865A, 0x865C, 0x865C, 0x865E, 0x865F, 0x8667, 0x8667, + 0x866B, 0x866B, 0x8671, 0x8671, 0x8679, 0x8679, 0x867B, 0x867B, 0x868A, 0x868C, 0x8693, 0x8693, 0x8695, 0x8695, 0x86A3, 0x86A4, + 0x86A9, 0x86AB, 0x86AF, 0x86B0, 0x86B6, 0x86B6, 0x86C4, 0x86C4, 0x86C6, 0x86C7, 0x86C9, 0x86C9, 0x86CB, 0x86CB, 0x86CD, 0x86CE, + 0x86D4, 0x86D4, 0x86D9, 0x86D9, 0x86DB, 0x86DB, 0x86DE, 0x86DF, 0x86E4, 0x86E4, 0x86E9, 0x86E9, 0x86EC, 0x86EF, 0x86F8, 0x86F9, + 0x86FB, 0x86FB, 0x86FE, 0x86FE, 0x8700, 0x8700, 0x8702, 0x8703, 0x8706, 0x8706, 0x8708, 0x870A, 0x870D, 0x870D, 0x8711, 0x8712, + 0x8718, 0x8718, 0x871A, 0x871A, 0x871C, 0x871C, 0x8725, 0x8725, 0x8729, 0x8729, 0x8734, 0x8734, 0x8737, 0x8737, 0x873B, 0x873B, + 0x873F, 0x873F, 0x8749, 0x8749, 0x874B, 0x874C, 0x874E, 0x874E, 0x8753, 0x8753, 0x8755, 0x8755, 0x8757, 0x8757, 0x8759, 0x8759, + 0x875F, 0x8760, 0x8763, 0x8763, 0x8766, 0x8766, 0x8768, 0x8768, 0x876A, 0x876A, 0x876E, 0x876E, 0x8774, 0x8774, 0x8776, 0x8776, + 0x8778, 0x8778, 0x877F, 0x877F, 0x8782, 0x8782, 0x878D, 0x878D, 0x879F, 0x879F, 0x87A2, 0x87A2, 0x87AB, 0x87AB, 0x87AF, 0x87AF, + 0x87B3, 0x87B3, 0x87BA, 0x87BB, 0x87BD, 0x87BD, 0x87C0, 0x87C0, 0x87C4, 0x87C4, 0x87C6, 0x87C7, 0x87CB, 0x87CB, 0x87D0, 0x87D0, + 0x87D2, 0x87D2, 0x87E0, 0x87E0, 0x87EF, 0x87EF, 0x87F2, 0x87F2, 0x87F6, 0x87F7, 0x87F9, 0x87F9, 0x87FB, 0x87FB, 0x87FE, 0x87FE, + 0x8805, 0x8805, 0x880D, 0x880F, 0x8811, 0x8811, 0x8815, 0x8816, 0x8821, 0x8823, 0x8827, 0x8827, 0x8831, 0x8831, 0x8836, 0x8836, + 0x8839, 0x8839, 0x883B, 0x883B, 0x8840, 0x8840, 0x8842, 0x8842, 0x8844, 0x8844, 0x8846, 0x8846, 0x884C, 0x884D, 0x8852, 0x8853, + 0x8857, 0x8857, 0x8859, 0x8859, 0x885B, 0x885B, 0x885D, 0x885E, 0x8861, 0x8863, 0x8868, 0x8868, 0x886B, 0x886B, 0x8870, 0x8870, + 0x8872, 0x8872, 0x8875, 0x8875, 0x8877, 0x8877, 0x887D, 0x887F, 0x8881, 0x8882, 0x8888, 0x8888, 0x888B, 0x888B, 0x888D, 0x888D, + 0x8892, 0x8892, 0x8896, 0x8897, 0x8899, 0x8899, 0x889E, 0x889E, 0x88A2, 0x88A2, 0x88A4, 0x88A4, 0x88AB, 0x88AB, 0x88AE, 0x88AE, + 0x88B0, 0x88B1, 0x88B4, 0x88B5, 0x88B7, 0x88B7, 0x88BF, 0x88BF, 0x88C1, 0x88C5, 0x88CF, 0x88CF, 0x88D4, 0x88D5, 0x88D8, 0x88D9, + 0x88DC, 0x88DD, 0x88DF, 0x88DF, 0x88E1, 0x88E1, 0x88E8, 0x88E8, 0x88F2, 0x88F4, 0x88F8, 0x88F9, 0x88FC, 0x88FE, 0x8902, 0x8902, + 0x8904, 0x8904, 0x8907, 0x8907, 0x890A, 0x890A, 0x890C, 0x890C, 0x8910, 0x8910, 0x8912, 0x8913, 0x891D, 0x891E, 0x8925, 0x8925, + 0x892A, 0x892B, 0x8936, 0x8936, 0x8938, 0x8938, 0x893B, 0x893B, 0x8941, 0x8941, 0x8943, 0x8944, 0x894C, 0x894D, 0x8956, 0x8956, + 0x895E, 0x8960, 0x8964, 0x8964, 0x8966, 0x8966, 0x896A, 0x896A, 0x896D, 0x896D, 0x896F, 0x896F, 0x8972, 0x8972, 0x8974, 0x8974, + 0x8977, 0x8977, 0x897E, 0x897F, 0x8981, 0x8981, 0x8983, 0x8983, 0x8986, 0x8988, 0x898A, 0x898B, 0x898F, 0x898F, 0x8993, 0x8993, + 0x8996, 0x8998, 0x899A, 0x899A, 0x89A1, 0x89A1, 0x89A6, 0x89A7, 0x89A9, 0x89AA, 0x89AC, 0x89AC, 0x89AF, 0x89AF, 0x89B2, 0x89B3, + 0x89BA, 0x89BA, 0x89BD, 0x89BD, 0x89BF, 0x89C0, 0x89D2, 0x89D2, 0x89DA, 0x89DA, 0x89DC, 0x89DD, 0x89E3, 0x89E3, 0x89E6, 0x89E7, + 0x89F4, 0x89F4, 0x89F8, 0x89F8, 0x8A00, 0x8A00, 0x8A02, 0x8A03, 0x8A08, 0x8A08, 0x8A0A, 0x8A0A, 0x8A0C, 0x8A0C, 0x8A0E, 0x8A0E, + 0x8A10, 0x8A10, 0x8A13, 0x8A13, 0x8A16, 0x8A18, 0x8A1B, 0x8A1B, 0x8A1D, 0x8A1D, 0x8A1F, 0x8A1F, 0x8A23, 0x8A23, 0x8A25, 0x8A25, + 0x8A2A, 0x8A2A, 0x8A2D, 0x8A2D, 0x8A31, 0x8A31, 0x8A33, 0x8A34, 0x8A36, 0x8A36, 0x8A3A, 0x8A3C, 0x8A41, 0x8A41, 0x8A46, 0x8A46, + 0x8A48, 0x8A48, 0x8A50, 0x8A52, 0x8A54, 0x8A55, 0x8A5B, 0x8A5B, 0x8A5E, 0x8A5E, 0x8A60, 0x8A60, 0x8A62, 0x8A63, 0x8A66, 0x8A66, + 0x8A69, 0x8A69, 0x8A6B, 0x8A6E, 0x8A70, 0x8A73, 0x8A7C, 0x8A7C, 0x8A82, 0x8A82, 0x8A84, 0x8A85, 0x8A87, 0x8A87, 0x8A89, 0x8A89, + 0x8A8C, 0x8A8D, 0x8A91, 0x8A91, 0x8A93, 0x8A93, 0x8A95, 0x8A95, 0x8A98, 0x8A98, 0x8A9A, 0x8A9A, 0x8A9E, 0x8A9E, 0x8AA0, 0x8AA1, + 0x8AA3, 0x8AA6, 0x8AA8, 0x8AA8, 0x8AAC, 0x8AAD, 0x8AB0, 0x8AB0, 0x8AB2, 0x8AB2, 0x8AB9, 0x8AB9, 0x8ABC, 0x8ABC, 0x8ABF, 0x8ABF, + 0x8AC2, 0x8AC2, 0x8AC4, 0x8AC4, 0x8AC7, 0x8AC7, 0x8ACB, 0x8ACD, 0x8ACF, 0x8ACF, 0x8AD2, 0x8AD2, 0x8AD6, 0x8AD6, 0x8ADA, 0x8ADC, + 0x8ADE, 0x8ADE, 0x8AE0, 0x8AE2, 0x8AE4, 0x8AE4, 0x8AE6, 0x8AE7, 0x8AEB, 0x8AEB, 0x8AED, 0x8AEE, 0x8AF1, 0x8AF1, 0x8AF3, 0x8AF3, + 0x8AF7, 0x8AF8, 0x8AFA, 0x8AFA, 0x8AFE, 0x8AFE, 0x8B00, 0x8B02, 0x8B04, 0x8B04, 0x8B07, 0x8B07, 0x8B0C, 0x8B0C, 0x8B0E, 0x8B0E, + 0x8B10, 0x8B10, 0x8B14, 0x8B14, 0x8B16, 0x8B17, 0x8B19, 0x8B1B, 0x8B1D, 0x8B1D, 0x8B20, 0x8B21, 0x8B26, 0x8B26, 0x8B28, 0x8B28, + 0x8B2B, 0x8B2C, 0x8B33, 0x8B33, 0x8B39, 0x8B39, 0x8B3E, 0x8B3E, 0x8B41, 0x8B41, 0x8B49, 0x8B49, 0x8B4C, 0x8B4C, 0x8B4E, 0x8B4F, + 0x8B56, 0x8B56, 0x8B58, 0x8B58, 0x8B5A, 0x8B5C, 0x8B5F, 0x8B5F, 0x8B66, 0x8B66, 0x8B6B, 0x8B6C, 0x8B6F, 0x8B72, 0x8B74, 0x8B74, + 0x8B77, 0x8B77, 0x8B7D, 0x8B7D, 0x8B80, 0x8B80, 0x8B83, 0x8B83, 0x8B8A, 0x8B8A, 0x8B8C, 0x8B8C, 0x8B8E, 0x8B8E, 0x8B90, 0x8B90, + 0x8B92, 0x8B93, 0x8B96, 0x8B96, 0x8B99, 0x8B9A, 0x8C37, 0x8C37, 0x8C3A, 0x8C3A, 0x8C3F, 0x8C3F, 0x8C41, 0x8C41, 0x8C46, 0x8C46, + 0x8C48, 0x8C48, 0x8C4A, 0x8C4A, 0x8C4C, 0x8C4C, 0x8C4E, 0x8C4E, 0x8C50, 0x8C50, 0x8C55, 0x8C55, 0x8C5A, 0x8C5A, 0x8C61, 0x8C62, + 0x8C6A, 0x8C6C, 0x8C78, 0x8C7A, 0x8C7C, 0x8C7C, 0x8C82, 0x8C82, 0x8C85, 0x8C85, 0x8C89, 0x8C8A, 0x8C8C, 0x8C8E, 0x8C94, 0x8C94, + 0x8C98, 0x8C98, 0x8C9D, 0x8C9E, 0x8CA0, 0x8CA2, 0x8CA7, 0x8CB0, 0x8CB2, 0x8CB4, 0x8CB6, 0x8CB8, 0x8CBB, 0x8CBD, 0x8CBF, 0x8CC4, + 0x8CC7, 0x8CC8, 0x8CCA, 0x8CCA, 0x8CCD, 0x8CCE, 0x8CD1, 0x8CD1, 0x8CD3, 0x8CD3, 0x8CDA, 0x8CDC, 0x8CDE, 0x8CDE, 0x8CE0, 0x8CE0, + 0x8CE2, 0x8CE4, 0x8CE6, 0x8CE6, 0x8CEA, 0x8CEA, 0x8CED, 0x8CED, 0x8CFA, 0x8CFD, 0x8D04, 0x8D05, 0x8D07, 0x8D08, 0x8D0A, 0x8D0B, + 0x8D0D, 0x8D0D, 0x8D0F, 0x8D10, 0x8D13, 0x8D14, 0x8D16, 0x8D16, 0x8D64, 0x8D64, 0x8D66, 0x8D67, 0x8D6B, 0x8D6B, 0x8D6D, 0x8D6D, + 0x8D70, 0x8D71, 0x8D73, 0x8D74, 0x8D77, 0x8D77, 0x8D81, 0x8D81, 0x8D85, 0x8D85, 0x8D8A, 0x8D8A, 0x8D99, 0x8D99, 0x8DA3, 0x8DA3, + 0x8DA8, 0x8DA8, 0x8DB3, 0x8DB3, 0x8DBA, 0x8DBA, 0x8DBE, 0x8DBE, 0x8DC2, 0x8DC2, 0x8DCB, 0x8DCC, 0x8DCF, 0x8DCF, 0x8DD6, 0x8DD6, + 0x8DDA, 0x8DDB, 0x8DDD, 0x8DDD, 0x8DDF, 0x8DDF, 0x8DE1, 0x8DE1, 0x8DE3, 0x8DE3, 0x8DE8, 0x8DE8, 0x8DEA, 0x8DEB, 0x8DEF, 0x8DEF, + 0x8DF3, 0x8DF3, 0x8DF5, 0x8DF5, 0x8DFC, 0x8DFC, 0x8DFF, 0x8DFF, 0x8E08, 0x8E0A, 0x8E0F, 0x8E10, 0x8E1D, 0x8E1F, 0x8E2A, 0x8E2A, + 0x8E30, 0x8E30, 0x8E34, 0x8E35, 0x8E42, 0x8E42, 0x8E44, 0x8E44, 0x8E47, 0x8E4A, 0x8E4C, 0x8E4C, 0x8E50, 0x8E50, 0x8E55, 0x8E55, + 0x8E59, 0x8E59, 0x8E5F, 0x8E60, 0x8E63, 0x8E64, 0x8E72, 0x8E72, 0x8E74, 0x8E74, 0x8E76, 0x8E76, 0x8E7C, 0x8E7C, 0x8E81, 0x8E81, + 0x8E84, 0x8E85, 0x8E87, 0x8E87, 0x8E8A, 0x8E8B, 0x8E8D, 0x8E8D, 0x8E91, 0x8E91, 0x8E93, 0x8E94, 0x8E99, 0x8E99, 0x8EA1, 0x8EA1, + 0x8EAA, 0x8EAC, 0x8EAF, 0x8EB1, 0x8EBE, 0x8EBE, 0x8EC5, 0x8EC6, 0x8EC8, 0x8EC8, 0x8ECA, 0x8ECD, 0x8ED2, 0x8ED2, 0x8EDB, 0x8EDB, + 0x8EDF, 0x8EDF, 0x8EE2, 0x8EE3, 0x8EEB, 0x8EEB, 0x8EF8, 0x8EF8, 0x8EFB, 0x8EFE, 0x8F03, 0x8F03, 0x8F05, 0x8F05, 0x8F09, 0x8F0A, + 0x8F0C, 0x8F0C, 0x8F12, 0x8F15, 0x8F19, 0x8F19, 0x8F1B, 0x8F1D, 0x8F1F, 0x8F1F, 0x8F26, 0x8F26, 0x8F29, 0x8F2A, 0x8F2F, 0x8F2F, + 0x8F33, 0x8F33, 0x8F38, 0x8F39, 0x8F3B, 0x8F3B, 0x8F3E, 0x8F3F, 0x8F42, 0x8F42, 0x8F44, 0x8F46, 0x8F49, 0x8F49, 0x8F4C, 0x8F4E, + 0x8F57, 0x8F57, 0x8F5C, 0x8F5C, 0x8F5F, 0x8F5F, 0x8F61, 0x8F64, 0x8F9B, 0x8F9C, 0x8F9E, 0x8F9F, 0x8FA3, 0x8FA3, 0x8FA7, 0x8FA8, + 0x8FAD, 0x8FB2, 0x8FB7, 0x8FB7, 0x8FBA, 0x8FBC, 0x8FBF, 0x8FBF, 0x8FC2, 0x8FC2, 0x8FC4, 0x8FC5, 0x8FCE, 0x8FCE, 0x8FD1, 0x8FD1, + 0x8FD4, 0x8FD4, 0x8FDA, 0x8FDA, 0x8FE2, 0x8FE2, 0x8FE5, 0x8FE6, 0x8FE9, 0x8FEB, 0x8FED, 0x8FED, 0x8FEF, 0x8FF0, 0x8FF4, 0x8FF4, + 0x8FF7, 0x8FFA, 0x8FFD, 0x8FFD, 0x9000, 0x9001, 0x9003, 0x9003, 0x9005, 0x9006, 0x900B, 0x900B, 0x900D, 0x9011, 0x9013, 0x9017, + 0x9019, 0x901A, 0x901D, 0x9023, 0x9027, 0x9027, 0x902E, 0x902E, 0x9031, 0x9032, 0x9035, 0x9036, 0x9038, 0x9039, 0x903C, 0x903C, + 0x903E, 0x903E, 0x9041, 0x9042, 0x9045, 0x9045, 0x9047, 0x9047, 0x9049, 0x904B, 0x904D, 0x9056, 0x9058, 0x9059, 0x905C, 0x905C, + 0x905E, 0x905E, 0x9060, 0x9061, 0x9063, 0x9063, 0x9065, 0x9065, 0x9068, 0x9069, 0x906D, 0x906F, 0x9072, 0x9072, 0x9075, 0x9078, + 0x907A, 0x907A, 0x907C, 0x907D, 0x907F, 0x9084, 0x9087, 0x9087, 0x9089, 0x908A, 0x908F, 0x908F, 0x9091, 0x9091, 0x90A3, 0x90A3, + 0x90A6, 0x90A6, 0x90A8, 0x90A8, 0x90AA, 0x90AA, 0x90AF, 0x90AF, 0x90B1, 0x90B1, 0x90B5, 0x90B5, 0x90B8, 0x90B8, 0x90C1, 0x90C1, + 0x90CA, 0x90CA, 0x90CE, 0x90CE, 0x90DB, 0x90DB, 0x90E1, 0x90E2, 0x90E4, 0x90E4, 0x90E8, 0x90E8, 0x90ED, 0x90ED, 0x90F5, 0x90F5, + 0x90F7, 0x90F7, 0x90FD, 0x90FD, 0x9102, 0x9102, 0x9112, 0x9112, 0x9119, 0x9119, 0x912D, 0x912D, 0x9130, 0x9130, 0x9132, 0x9132, + 0x9149, 0x914E, 0x9152, 0x9152, 0x9154, 0x9154, 0x9156, 0x9156, 0x9158, 0x9158, 0x9162, 0x9163, 0x9165, 0x9165, 0x9169, 0x916A, + 0x916C, 0x916C, 0x9172, 0x9173, 0x9175, 0x9175, 0x9177, 0x9178, 0x9182, 0x9182, 0x9187, 0x9187, 0x9189, 0x9189, 0x918B, 0x918B, + 0x918D, 0x918D, 0x9190, 0x9190, 0x9192, 0x9192, 0x9197, 0x9197, 0x919C, 0x919C, 0x91A2, 0x91A2, 0x91A4, 0x91A4, 0x91AA, 0x91AB, + 0x91AF, 0x91AF, 0x91B4, 0x91B5, 0x91B8, 0x91B8, 0x91BA, 0x91BA, 0x91C0, 0x91C1, 0x91C6, 0x91C9, 0x91CB, 0x91D1, 0x91D6, 0x91D6, + 0x91D8, 0x91D8, 0x91DB, 0x91DD, 0x91DF, 0x91DF, 0x91E1, 0x91E1, 0x91E3, 0x91E3, 0x91E6, 0x91E7, 0x91F5, 0x91F6, 0x91FC, 0x91FC, + 0x91FF, 0x91FF, 0x920D, 0x920E, 0x9211, 0x9211, 0x9214, 0x9215, 0x921E, 0x921E, 0x9229, 0x9229, 0x922C, 0x922C, 0x9234, 0x9234, + 0x9237, 0x9237, 0x923F, 0x923F, 0x9244, 0x9245, 0x9248, 0x9249, 0x924B, 0x924B, 0x9250, 0x9250, 0x9257, 0x9257, 0x925A, 0x925B, + 0x925E, 0x925E, 0x9262, 0x9262, 0x9264, 0x9264, 0x9266, 0x9266, 0x9271, 0x9271, 0x927E, 0x927E, 0x9280, 0x9280, 0x9283, 0x9283, + 0x9285, 0x9285, 0x9291, 0x9291, 0x9293, 0x9293, 0x9295, 0x9296, 0x9298, 0x9298, 0x929A, 0x929C, 0x92AD, 0x92AD, 0x92B7, 0x92B7, + 0x92B9, 0x92B9, 0x92CF, 0x92CF, 0x92D2, 0x92D2, 0x92E4, 0x92E4, 0x92E9, 0x92EA, 0x92ED, 0x92ED, 0x92F2, 0x92F3, 0x92F8, 0x92F8, + 0x92FA, 0x92FA, 0x92FC, 0x92FC, 0x9306, 0x9306, 0x930F, 0x9310, 0x9318, 0x931A, 0x9320, 0x9320, 0x9322, 0x9323, 0x9326, 0x9326, + 0x9328, 0x9328, 0x932B, 0x932C, 0x932E, 0x932F, 0x9332, 0x9332, 0x9335, 0x9335, 0x933A, 0x933B, 0x9344, 0x9344, 0x934B, 0x934B, + 0x934D, 0x934D, 0x9354, 0x9354, 0x9356, 0x9356, 0x935B, 0x935C, 0x9360, 0x9360, 0x936C, 0x936C, 0x936E, 0x936E, 0x9375, 0x9375, + 0x937C, 0x937C, 0x937E, 0x937E, 0x938C, 0x938C, 0x9394, 0x9394, 0x9396, 0x9397, 0x939A, 0x939A, 0x93A7, 0x93A7, 0x93AC, 0x93AE, + 0x93B0, 0x93B0, 0x93B9, 0x93B9, 0x93C3, 0x93C3, 0x93C8, 0x93C8, 0x93D0, 0x93D1, 0x93D6, 0x93D8, 0x93DD, 0x93DD, 0x93E1, 0x93E1, + 0x93E4, 0x93E5, 0x93E8, 0x93E8, 0x9403, 0x9403, 0x9407, 0x9407, 0x9410, 0x9410, 0x9413, 0x9414, 0x9418, 0x941A, 0x9421, 0x9421, + 0x942B, 0x942B, 0x9435, 0x9436, 0x9438, 0x9438, 0x943A, 0x943A, 0x9441, 0x9441, 0x9444, 0x9444, 0x9451, 0x9453, 0x945A, 0x945B, + 0x945E, 0x945E, 0x9460, 0x9460, 0x9462, 0x9462, 0x946A, 0x946A, 0x9470, 0x9470, 0x9475, 0x9475, 0x9477, 0x9477, 0x947C, 0x947F, + 0x9481, 0x9481, 0x9577, 0x9577, 0x9580, 0x9580, 0x9582, 0x9583, 0x9587, 0x9587, 0x9589, 0x958B, 0x958F, 0x958F, 0x9591, 0x9591, + 0x9593, 0x9594, 0x9596, 0x9596, 0x9598, 0x9599, 0x95A0, 0x95A0, 0x95A2, 0x95A5, 0x95A7, 0x95A8, 0x95AD, 0x95AD, 0x95B2, 0x95B2, + 0x95B9, 0x95B9, 0x95BB, 0x95BC, 0x95BE, 0x95BE, 0x95C3, 0x95C3, 0x95C7, 0x95C7, 0x95CA, 0x95CA, 0x95CC, 0x95CD, 0x95D4, 0x95D6, + 0x95D8, 0x95D8, 0x95DC, 0x95DC, 0x95E1, 0x95E2, 0x95E5, 0x95E5, 0x961C, 0x961C, 0x9621, 0x9621, 0x9628, 0x9628, 0x962A, 0x962A, + 0x962E, 0x962F, 0x9632, 0x9632, 0x963B, 0x963B, 0x963F, 0x9640, 0x9642, 0x9642, 0x9644, 0x9644, 0x964B, 0x964D, 0x964F, 0x9650, + 0x965B, 0x965F, 0x9662, 0x9666, 0x966A, 0x966A, 0x966C, 0x966C, 0x9670, 0x9670, 0x9672, 0x9673, 0x9675, 0x9678, 0x967A, 0x967A, + 0x967D, 0x967D, 0x9685, 0x9686, 0x9688, 0x9688, 0x968A, 0x968B, 0x968D, 0x968F, 0x9694, 0x9695, 0x9697, 0x9699, 0x969B, 0x969C, + 0x96A0, 0x96A0, 0x96A3, 0x96A3, 0x96A7, 0x96A8, 0x96AA, 0x96AA, 0x96B0, 0x96B2, 0x96B4, 0x96B4, 0x96B6, 0x96B9, 0x96BB, 0x96BC, + 0x96C0, 0x96C1, 0x96C4, 0x96C7, 0x96C9, 0x96C9, 0x96CB, 0x96CE, 0x96D1, 0x96D1, 0x96D5, 0x96D6, 0x96D9, 0x96D9, 0x96DB, 0x96DC, + 0x96E2, 0x96E3, 0x96E8, 0x96E8, 0x96EA, 0x96EB, 0x96F0, 0x96F0, 0x96F2, 0x96F2, 0x96F6, 0x96F7, 0x96F9, 0x96F9, 0x96FB, 0x96FB, + 0x9700, 0x9700, 0x9704, 0x9704, 0x9706, 0x9708, 0x970A, 0x970A, 0x970D, 0x970F, 0x9711, 0x9711, 0x9713, 0x9713, 0x9716, 0x9716, + 0x9719, 0x9719, 0x971C, 0x971C, 0x971E, 0x971E, 0x9724, 0x9724, 0x9727, 0x9727, 0x972A, 0x972A, 0x9730, 0x9730, 0x9732, 0x9732, + 0x9738, 0x9739, 0x973D, 0x973E, 0x9742, 0x9742, 0x9744, 0x9744, 0x9746, 0x9746, 0x9748, 0x9749, 0x9752, 0x9752, 0x9756, 0x9756, + 0x9759, 0x9759, 0x975C, 0x975C, 0x975E, 0x975E, 0x9760, 0x9762, 0x9764, 0x9764, 0x9766, 0x9766, 0x9768, 0x9769, 0x976B, 0x976B, + 0x976D, 0x976D, 0x9771, 0x9771, 0x9774, 0x9774, 0x9779, 0x977A, 0x977C, 0x977C, 0x9781, 0x9781, 0x9784, 0x9786, 0x978B, 0x978B, + 0x978D, 0x978D, 0x978F, 0x9790, 0x9798, 0x9798, 0x979C, 0x979C, 0x97A0, 0x97A0, 0x97A3, 0x97A3, 0x97A6, 0x97A6, 0x97A8, 0x97A8, + 0x97AB, 0x97AB, 0x97AD, 0x97AD, 0x97B3, 0x97B4, 0x97C3, 0x97C3, 0x97C6, 0x97C6, 0x97C8, 0x97C8, 0x97CB, 0x97CB, 0x97D3, 0x97D3, + 0x97DC, 0x97DC, 0x97ED, 0x97EE, 0x97F2, 0x97F3, 0x97F5, 0x97F6, 0x97FB, 0x97FB, 0x97FF, 0x97FF, 0x9801, 0x9803, 0x9805, 0x9806, + 0x9808, 0x9808, 0x980C, 0x980C, 0x980F, 0x9813, 0x9817, 0x9818, 0x981A, 0x981A, 0x9821, 0x9821, 0x9824, 0x9824, 0x982C, 0x982D, + 0x9834, 0x9834, 0x9837, 0x9838, 0x983B, 0x983D, 0x9846, 0x9846, 0x984B, 0x984F, 0x9854, 0x9855, 0x9858, 0x9858, 0x985B, 0x985B, + 0x985E, 0x985E, 0x9867, 0x9867, 0x986B, 0x986B, 0x986F, 0x9871, 0x9873, 0x9874, 0x98A8, 0x98A8, 0x98AA, 0x98AA, 0x98AF, 0x98AF, + 0x98B1, 0x98B1, 0x98B6, 0x98B6, 0x98C3, 0x98C4, 0x98C6, 0x98C6, 0x98DB, 0x98DC, 0x98DF, 0x98DF, 0x98E2, 0x98E2, 0x98E9, 0x98E9, + 0x98EB, 0x98EB, 0x98ED, 0x98EF, 0x98F2, 0x98F2, 0x98F4, 0x98F4, 0x98FC, 0x98FE, 0x9903, 0x9903, 0x9905, 0x9905, 0x9909, 0x990A, + 0x990C, 0x990C, 0x9910, 0x9910, 0x9912, 0x9914, 0x9918, 0x9918, 0x991D, 0x991E, 0x9920, 0x9921, 0x9924, 0x9924, 0x9928, 0x9928, + 0x992C, 0x992C, 0x992E, 0x992E, 0x993D, 0x993E, 0x9942, 0x9942, 0x9945, 0x9945, 0x9949, 0x9949, 0x994B, 0x994C, 0x9950, 0x9952, + 0x9955, 0x9955, 0x9957, 0x9957, 0x9996, 0x9999, 0x99A5, 0x99A5, 0x99A8, 0x99A8, 0x99AC, 0x99AE, 0x99B3, 0x99B4, 0x99BC, 0x99BC, + 0x99C1, 0x99C1, 0x99C4, 0x99C6, 0x99C8, 0x99C8, 0x99D0, 0x99D2, 0x99D5, 0x99D5, 0x99D8, 0x99D8, 0x99DB, 0x99DB, 0x99DD, 0x99DD, + 0x99DF, 0x99DF, 0x99E2, 0x99E2, 0x99ED, 0x99EE, 0x99F1, 0x99F2, 0x99F8, 0x99F8, 0x99FB, 0x99FB, 0x99FF, 0x99FF, 0x9A01, 0x9A01, + 0x9A05, 0x9A05, 0x9A0E, 0x9A0F, 0x9A12, 0x9A13, 0x9A19, 0x9A19, 0x9A28, 0x9A28, 0x9A2B, 0x9A2B, 0x9A30, 0x9A30, 0x9A37, 0x9A37, + 0x9A3E, 0x9A3E, 0x9A40, 0x9A40, 0x9A42, 0x9A43, 0x9A45, 0x9A45, 0x9A4D, 0x9A4D, 0x9A55, 0x9A55, 0x9A57, 0x9A57, 0x9A5A, 0x9A5B, + 0x9A5F, 0x9A5F, 0x9A62, 0x9A62, 0x9A64, 0x9A65, 0x9A69, 0x9A6B, 0x9AA8, 0x9AA8, 0x9AAD, 0x9AAD, 0x9AB0, 0x9AB0, 0x9AB8, 0x9AB8, + 0x9ABC, 0x9ABC, 0x9AC0, 0x9AC0, 0x9AC4, 0x9AC4, 0x9ACF, 0x9ACF, 0x9AD1, 0x9AD1, 0x9AD3, 0x9AD4, 0x9AD8, 0x9AD8, 0x9ADE, 0x9ADF, + 0x9AE2, 0x9AE3, 0x9AE6, 0x9AE6, 0x9AEA, 0x9AEB, 0x9AED, 0x9AEF, 0x9AF1, 0x9AF1, 0x9AF4, 0x9AF4, 0x9AF7, 0x9AF7, 0x9AFB, 0x9AFB, + 0x9B06, 0x9B06, 0x9B18, 0x9B18, 0x9B1A, 0x9B1A, 0x9B1F, 0x9B1F, 0x9B22, 0x9B23, 0x9B25, 0x9B25, 0x9B27, 0x9B2A, 0x9B2E, 0x9B2F, + 0x9B31, 0x9B32, 0x9B3B, 0x9B3C, 0x9B41, 0x9B45, 0x9B4D, 0x9B4F, 0x9B51, 0x9B51, 0x9B54, 0x9B54, 0x9B58, 0x9B58, 0x9B5A, 0x9B5A, + 0x9B6F, 0x9B6F, 0x9B74, 0x9B74, 0x9B83, 0x9B83, 0x9B8E, 0x9B8E, 0x9B91, 0x9B93, 0x9B96, 0x9B97, 0x9B9F, 0x9BA0, 0x9BA8, 0x9BA8, + 0x9BAA, 0x9BAB, 0x9BAD, 0x9BAE, 0x9BB4, 0x9BB4, 0x9BB9, 0x9BB9, 0x9BC0, 0x9BC0, 0x9BC6, 0x9BC6, 0x9BC9, 0x9BCA, 0x9BCF, 0x9BCF, + 0x9BD1, 0x9BD2, 0x9BD4, 0x9BD4, 0x9BD6, 0x9BD6, 0x9BDB, 0x9BDB, 0x9BE1, 0x9BE4, 0x9BE8, 0x9BE8, 0x9BF0, 0x9BF2, 0x9BF5, 0x9BF5, + 0x9C04, 0x9C04, 0x9C06, 0x9C06, 0x9C08, 0x9C0A, 0x9C0C, 0x9C0D, 0x9C10, 0x9C10, 0x9C12, 0x9C15, 0x9C1B, 0x9C1B, 0x9C21, 0x9C21, + 0x9C24, 0x9C25, 0x9C2D, 0x9C30, 0x9C32, 0x9C32, 0x9C39, 0x9C3B, 0x9C3E, 0x9C3E, 0x9C46, 0x9C48, 0x9C52, 0x9C52, 0x9C57, 0x9C57, + 0x9C5A, 0x9C5A, 0x9C60, 0x9C60, 0x9C67, 0x9C67, 0x9C76, 0x9C76, 0x9C78, 0x9C78, 0x9CE5, 0x9CE5, 0x9CE7, 0x9CE7, 0x9CE9, 0x9CE9, + 0x9CEB, 0x9CEC, 0x9CF0, 0x9CF0, 0x9CF3, 0x9CF4, 0x9CF6, 0x9CF6, 0x9D03, 0x9D03, 0x9D06, 0x9D09, 0x9D0E, 0x9D0E, 0x9D12, 0x9D12, + 0x9D15, 0x9D15, 0x9D1B, 0x9D1B, 0x9D1F, 0x9D1F, 0x9D23, 0x9D23, 0x9D26, 0x9D26, 0x9D28, 0x9D28, 0x9D2A, 0x9D2C, 0x9D3B, 0x9D3B, + 0x9D3E, 0x9D3F, 0x9D41, 0x9D41, 0x9D44, 0x9D44, 0x9D46, 0x9D46, 0x9D48, 0x9D48, 0x9D50, 0x9D51, 0x9D59, 0x9D59, 0x9D5C, 0x9D5E, + 0x9D60, 0x9D61, 0x9D64, 0x9D64, 0x9D6C, 0x9D6C, 0x9D6F, 0x9D6F, 0x9D72, 0x9D72, 0x9D7A, 0x9D7A, 0x9D87, 0x9D87, 0x9D89, 0x9D89, + 0x9D8F, 0x9D8F, 0x9D9A, 0x9D9A, 0x9DA4, 0x9DA4, 0x9DA9, 0x9DA9, 0x9DAB, 0x9DAB, 0x9DAF, 0x9DAF, 0x9DB2, 0x9DB2, 0x9DB4, 0x9DB4, + 0x9DB8, 0x9DB8, 0x9DBA, 0x9DBB, 0x9DC1, 0x9DC2, 0x9DC4, 0x9DC4, 0x9DC6, 0x9DC6, 0x9DCF, 0x9DCF, 0x9DD3, 0x9DD3, 0x9DD9, 0x9DD9, + 0x9DE6, 0x9DE6, 0x9DED, 0x9DED, 0x9DEF, 0x9DEF, 0x9DF2, 0x9DF2, 0x9DF8, 0x9DFA, 0x9DFD, 0x9DFD, 0x9E1A, 0x9E1B, 0x9E1E, 0x9E1E, + 0x9E75, 0x9E75, 0x9E78, 0x9E79, 0x9E7D, 0x9E7D, 0x9E7F, 0x9E7F, 0x9E81, 0x9E81, 0x9E88, 0x9E88, 0x9E8B, 0x9E8C, 0x9E91, 0x9E93, + 0x9E95, 0x9E95, 0x9E97, 0x9E97, 0x9E9D, 0x9E9D, 0x9E9F, 0x9E9F, 0x9EA5, 0x9EA6, 0x9EA9, 0x9EAA, 0x9EAD, 0x9EAD, 0x9EB8, 0x9EBC, + 0x9EBE, 0x9EBF, 0x9EC4, 0x9EC4, 0x9ECC, 0x9ED0, 0x9ED2, 0x9ED2, 0x9ED4, 0x9ED4, 0x9ED8, 0x9ED9, 0x9EDB, 0x9EDE, 0x9EE0, 0x9EE0, + 0x9EE5, 0x9EE5, 0x9EE8, 0x9EE8, 0x9EEF, 0x9EEF, 0x9EF4, 0x9EF4, 0x9EF6, 0x9EF7, 0x9EF9, 0x9EF9, 0x9EFB, 0x9EFD, 0x9F07, 0x9F08, + 0x9F0E, 0x9F0E, 0x9F13, 0x9F13, 0x9F15, 0x9F15, 0x9F20, 0x9F21, 0x9F2C, 0x9F2C, 0x9F3B, 0x9F3B, 0x9F3E, 0x9F3E, 0x9F4A, 0x9F4B, + 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 881ab2108..c34d047d9 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs @@ -3,369 +3,368 @@ 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 { - /// - /// 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; + 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 (b.FileName.Length == 1) + if (dontShowHidden && dir.Name[0] == '.') { - return -1; + continue; } - return string.Compare(a.FileName[1..], b.FileName[1..]); + this.files.Add(GetDir(dir, path)); } - if (a.Type != b.Type) + foreach (var file in dirInfo.EnumerateFiles().OrderBy(f => f.Name)) { - 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) + if (string.IsNullOrEmpty(file.Name)) { - this.files.Add(new FileStruct - { - Type = FileStructType.Directory, - FilePath = path, - FileName = "..", - FileSize = 0, - FileModifiedDate = string.Empty, - FormattedFileSize = string.Empty, - Ext = string.Empty, - }); + continue; } - var dirInfo = new DirectoryInfo(path); - - var dontShowHidden = this.flags.HasFlag(ImGuiFileDialogFlags.DontShowHiddenFiles); - - foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name)) + if (dontShowHidden && file.Name[0] == '.') { - if (string.IsNullOrEmpty(dir.Name)) + 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; } - - 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() - { - foreach (var drive in DriveInfo.GetDrives()) - { - this.drives.Add(new SideBarItem(drive.Name, drive.Name, FontAwesomeIcon.Server)); + this.files.Add(GetFile(file, path)); } - var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); - - this.quickAccess.Add(new SideBarItem("Desktop", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), FontAwesomeIcon.Desktop)); - this.quickAccess.Add(new SideBarItem("Documents", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), FontAwesomeIcon.File)); - - if (!string.IsNullOrEmpty(personal)) - { - this.quickAccess.Add(new SideBarItem("Downloads", Path.Combine(personal, "Downloads"), FontAwesomeIcon.Download)); - } - - this.quickAccess.Add(new SideBarItem("Favorites", Environment.GetFolderPath(Environment.SpecialFolder.Favorites), FontAwesomeIcon.Star)); - this.quickAccess.Add(new SideBarItem("Music", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), FontAwesomeIcon.Music)); - this.quickAccess.Add(new SideBarItem("Pictures", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), FontAwesomeIcon.Image)); - this.quickAccess.Add(new SideBarItem("Videos", Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), FontAwesomeIcon.Video)); - } - - 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(); + this.SortFields(this.currentSortingField); } } + + private void SetupSideBar() + { + foreach (var drive in DriveInfo.GetDrives()) + { + this.drives.Add(new SideBarItem(drive.Name, drive.Name, FontAwesomeIcon.Server)); + } + + var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + + this.quickAccess.Add(new SideBarItem("Desktop", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), FontAwesomeIcon.Desktop)); + this.quickAccess.Add(new SideBarItem("Documents", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), FontAwesomeIcon.File)); + + if (!string.IsNullOrEmpty(personal)) + { + this.quickAccess.Add(new SideBarItem("Downloads", Path.Combine(personal, "Downloads"), FontAwesomeIcon.Download)); + } + + this.quickAccess.Add(new SideBarItem("Favorites", Environment.GetFolderPath(Environment.SpecialFolder.Favorites), FontAwesomeIcon.Star)); + this.quickAccess.Add(new SideBarItem("Music", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), FontAwesomeIcon.Music)); + this.quickAccess.Add(new SideBarItem("Pictures", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), FontAwesomeIcon.Image)); + this.quickAccess.Add(new SideBarItem("Videos", Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), FontAwesomeIcon.Video)); + } + + 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 8671a2736..1373c9189 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs @@ -6,72 +6,71 @@ using System.Numerics; using Dalamud.Utility; -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 readonly struct SideBarItem + { + public SideBarItem(string text, string location, FontAwesomeIcon icon) { - public FileStructType Type; - public string FilePath; - public string FileName; - public string Ext; - public long FileSize; - public string FormattedFileSize; - public string FileModifiedDate; + this.Text = text; + this.Location = location; + this.Icon = icon; + this.Exists = !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location); } - private readonly struct SideBarItem + public string Text { get; init; } + + public string Location { get; init; } + + public FontAwesomeIcon Icon { get; init; } + + public bool Exists { get; init; } + + public bool CheckExistence() + => !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location); + } + + private struct FilterStruct + { + public string Filter; + public HashSet CollectionFilters; + + public void Clear() { - public SideBarItem(string text, string location, FontAwesomeIcon icon) - { - this.Text = text; - this.Location = location; - this.Icon = icon; - this.Exists = !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location); - } - - public string Text { get; init; } - - public string Location { get; init; } - - public FontAwesomeIcon Icon { get; init; } - - public bool Exists { get; init; } - - public bool CheckExistence() - => !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location); + this.Filter = string.Empty; + this.CollectionFilters.Clear(); } - private struct FilterStruct + public bool Empty() { - 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.Equals(filter, StringComparison.InvariantCultureIgnoreCase) || (this.CollectionFilters != null && this.CollectionFilters.Any(colFilter => colFilter.Equals(filter, StringComparison.InvariantCultureIgnoreCase))); - } + return string.IsNullOrEmpty(this.Filter) && (this.CollectionFilters == null || (this.CollectionFilters.Count == 0)); } - private struct IconColorItem + public bool FilterExists(string filter) { - public FontAwesomeIcon Icon; - public Vector4 Color; + return this.Filter.Equals(filter, StringComparison.InvariantCultureIgnoreCase) || (this.CollectionFilters != null && this.CollectionFilters.Any(colFilter => colFilter.Equals(filter, StringComparison.InvariantCultureIgnoreCase))); } } + + private struct IconColorItem + { + public FontAwesomeIcon Icon; + public Vector4 Color; + } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs index 5f51d2739..c55722b4a 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs @@ -5,561 +5,574 @@ 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(ImGuiHelpers.ScaledVector2(800, 500), ImGuiCond.FirstUseEver); + + if (this.isModal && !this.okResultToConfirm) { - if (!this.visible) return false; + ImGui.OpenPopup(name); + windowVisible = ImGui.BeginPopupModal(name, ref this.visible, this.WindowFlags); + } + else + { + windowVisible = ImGui.Begin(name, ref this.visible, this.WindowFlags); + } - var res = false; - var name = this.title + "###" + this.id; + bool wasClosed = false; + if (windowVisible) + { + if (!this.visible) + { // window closed + this.isOk = false; + wasClosed = true; + } + else + { + if (this.selectedFilter.Empty() && (this.filters.Count > 0)) + { + this.selectedFilter = this.filters[0]; + } - bool windowVisible; - this.isOk = false; - this.wantsToQuit = false; + if (this.files.Count == 0) + { + if (!string.IsNullOrEmpty(this.defaultFileName)) + { + this.SetDefaultFileName(); + this.SetSelectedFilterWithExt(this.defaultExtension); + } + else if (this.IsDirectoryMode()) + { + this.SetDefaultFileName(); + } - this.ResetEvents(); + this.ScanDir(this.currentPath); + } - ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(800, 500), ImGuiCond.FirstUseEver); + this.DrawHeader(); + this.DrawContent(); + res = this.DrawFooter(); + } if (this.isModal && !this.okResultToConfirm) { - ImGui.OpenPopup(name); - windowVisible = ImGui.BeginPopupModal(name, ref this.visible, this.WindowFlags); - } - else - { - windowVisible = ImGui.Begin(name, ref this.visible, this.WindowFlags); - } - - bool wasClosed = false; - if (windowVisible) - { - if (!this.visible) - { // window closed - this.isOk = false; - wasClosed = true; - } - else - { - if (this.selectedFilter.Empty() && (this.filters.Count > 0)) - { - 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 float Scaled(float value) - => value * ImGuiHelpers.GlobalScale; - - private static void AddToIconMap(string[] extensions, FontAwesomeIcon icon, Vector4 color) - { - foreach (var ext in extensions) - { - iconMap[ext] = new IconColorItem - { - Icon = icon, - Color = color, - }; + ImGui.EndPopup(); } } - private static IconColorItem GetIcon(string ext) + if (!this.isModal || this.okResultToConfirm) { - if (iconMap == null) - { - iconMap = new(); - AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, FontAwesomeIcon.FileVideo, miscTextColor); - AddToIconMap(new[] { "pdf" }, FontAwesomeIcon.FilePdf, miscTextColor); - AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, FontAwesomeIcon.FileImage, imageTextColor); - AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, FontAwesomeIcon.FileCode, codeTextColor); - AddToIconMap(new[] { "txt", "md" }, FontAwesomeIcon.FileAlt, standardTextColor); - AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, FontAwesomeIcon.FileArchive, miscTextColor); - AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, FontAwesomeIcon.FileAudio, miscTextColor); - AddToIconMap(new[] { "csv" }, FontAwesomeIcon.FileCsv, miscTextColor); - } + ImGui.End(); + } - return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem + return wasClosed || this.ConfirmOrOpenOverWriteFileDialogIfNeeded(res); + } + + private static float Scaled(float value) + => value * ImGuiHelpers.GlobalScale; + + private static void AddToIconMap(string[] extensions, FontAwesomeIcon icon, Vector4 color) + { + foreach (var ext in extensions) + { + iconMap[ext] = new IconColorItem { - Icon = FontAwesomeIcon.File, - Color = standardTextColor, + Icon = icon, + Color = color, }; } + } - private void DrawHeader() + private static IconColorItem GetIcon(string ext) + { + if (iconMap == null) { - this.DrawPathComposer(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(2)); - ImGui.Separator(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(2)); - - this.DrawSearchBar(); + iconMap = new(); + AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, FontAwesomeIcon.FileVideo, miscTextColor); + AddToIconMap(new[] { "pdf" }, FontAwesomeIcon.FilePdf, miscTextColor); + AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, FontAwesomeIcon.FileImage, imageTextColor); + AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, FontAwesomeIcon.FileCode, codeTextColor); + AddToIconMap(new[] { "txt", "md" }, FontAwesomeIcon.FileAlt, standardTextColor); + AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, FontAwesomeIcon.FileArchive, miscTextColor); + AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, FontAwesomeIcon.FileAudio, miscTextColor); + AddToIconMap(new[] { "csv" }, FontAwesomeIcon.FileCsv, miscTextColor); } - private void DrawPathComposer() + return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(this.pathInputActivated ? FontAwesomeIcon.Times.ToIconString() : FontAwesomeIcon.Edit.ToIconString())) - { - this.pathInputActivated = !this.pathInputActivated; - } + Icon = FontAwesomeIcon.File, + Color = standardTextColor, + }; + } - ImGui.PopFont(); + private void DrawHeader() + { + this.DrawPathComposer(); - ImGui.SameLine(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(2)); + ImGui.Separator(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(2)); - if (this.pathDecomposition.Count > 0) - { - if (this.pathInputActivated) - { - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255); - } - else - { - for (var idx = 0; idx < this.pathDecomposition.Count; idx++) - { - if (idx > 0) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() - Scaled(3)); - } + this.DrawSearchBar(); + } - 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 DrawPathComposer() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(this.pathInputActivated ? FontAwesomeIcon.Times.ToIconString() : FontAwesomeIcon.Edit.ToIconString())) + { + this.pathInputActivated = !this.pathInputActivated; } - private void DrawSearchBar() + ImGui.PopFont(); + + ImGui.SameLine(); + + if (this.pathDecomposition.Count > 0) { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Home.ToIconString())) + if (this.pathInputActivated) { - this.SetPath("."); - } - - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Reset to current directory"); - } - - ImGui.SameLine(); - - this.DrawDirectoryCreation(); - - if (!this.createDirectoryMode) - { - ImGui.SameLine(); - ImGui.TextUnformatted("Search :"); - ImGui.SameLine(); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255)) - { - this.ApplyFilteringOnFileList(); - } - } - } - - private void DrawDirectoryCreation() - { - if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return; - - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString()) && !this.createDirectoryMode) - { - this.createDirectoryMode = true; - this.createDirectoryBuffer = string.Empty; - } - - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Create Directory"); - } - - if (this.createDirectoryMode) - { - ImGui.SameLine(); - ImGui.TextUnformatted("New Directory Name"); - - ImGui.SameLine(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - Scaled(100)); - ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255); - - 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)) - { - if (ImGui.BeginChild("##FileDialog_ColumnChild", size)) - { - ImGui.Columns(2, "##FileDialog_Columns"); - - this.DrawSideBar(size with { X = Scaled(150) }); - - ImGui.SetColumnWidth(0, Scaled(150)); - ImGui.NextColumn(); - - this.DrawFileListView(size - new Vector2(Scaled(160), 0)); - - ImGui.Columns(1); - } - - ImGui.EndChild(); + ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255); } else { - this.DrawFileListView(size); - } - } - - private void DrawSideBar(Vector2 size) - { - if (ImGui.BeginChild("##FileDialog_SideBar", size)) - { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(5)); - - var idx = 0; - foreach (var qa in this.drives.Concat(this.quickAccess).Where(qa => qa.Exists)) + for (var idx = 0; idx < this.pathDecomposition.Count; idx++) { - ImGui.PushID(idx++); - ImGui.SetCursorPosX(Scaled(25)); - if (ImGui.Selectable(qa.Text, qa.Text == this.selectedSideBar) && qa.CheckExistence()) + if (idx > 0) { - this.SetPath(qa.Location); - this.selectedSideBar = qa.Text; + ImGui.SameLine(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() - Scaled(3)); } - ImGui.PushFont(UiBuilder.IconFont); - ImGui.SameLine(); - ImGui.SetCursorPosX(0); - ImGui.TextUnformatted(qa.Icon.ToIconString()); - - ImGui.PopFont(); + 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(FontAwesomeIcon.Home.ToIconString())) + { + this.SetPath("."); + } + + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Reset to current directory"); + } + + ImGui.SameLine(); + + this.DrawDirectoryCreation(); + + if (!this.createDirectoryMode) + { + ImGui.SameLine(); + ImGui.TextUnformatted("Search :"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255)) + { + this.ApplyFilteringOnFileList(); + } + } + } + + private void DrawDirectoryCreation() + { + if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return; + + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString()) && !this.createDirectoryMode) + { + this.createDirectoryMode = true; + this.createDirectoryBuffer = string.Empty; + } + + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Create Directory"); + } + + if (this.createDirectoryMode) + { + ImGui.SameLine(); + ImGui.TextUnformatted("New Directory Name"); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - Scaled(100)); + ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255); + + 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)) + { + if (ImGui.BeginChild("##FileDialog_ColumnChild", size)) + { + ImGui.Columns(2, "##FileDialog_Columns"); + + this.DrawSideBar(size with { X = Scaled(150) }); + + ImGui.SetColumnWidth(0, Scaled(150)); + ImGui.NextColumn(); + + this.DrawFileListView(size - new Vector2(Scaled(160), 0)); + + ImGui.Columns(1); + } ImGui.EndChild(); } - - private unsafe void DrawFileListView(Vector2 size) + else { - if (!ImGui.BeginChild("##FileDialog_FileList", size)) + this.DrawFileListView(size); + } + } + + private void DrawSideBar(Vector2 size) + { + if (ImGui.BeginChild("##FileDialog_SideBar", size)) + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + Scaled(5)); + + var idx = 0; + foreach (var qa in this.drives.Concat(this.quickAccess).Where(qa => qa.Exists)) { - ImGui.EndChild(); - return; - } - - const ImGuiTableFlags 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.PushID(idx++); + ImGui.SetCursorPosX(Scaled(25)); + if (ImGui.Selectable(qa.Text, qa.Text == this.selectedSideBar) && qa.CheckExistence()) { - 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); - } - } + this.SetPath(qa.Location); + this.selectedSideBar = qa.Text; } - if (this.filteredFiles.Count > 0) - { - ImGuiListClipperPtr clipper; - unsafe - { - clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - } + ImGui.PushFont(UiBuilder.IconFont); + ImGui.SameLine(); + ImGui.SetCursorPosX(0); + ImGui.TextUnformatted(qa.Icon.ToIconString()); - lock (this.filesLock) + ImGui.PopFont(); + ImGui.PopID(); + } + } + + ImGui.EndChild(); + } + + private unsafe void DrawFileListView(Vector2 size) + { + if (!ImGui.BeginChild("##FileDialog_FileList", size)) + { + ImGui.EndChild(); + return; + } + + const ImGuiTableFlags 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) { - clipper.Begin(this.filteredFiles.Count); - while (clipper.Step()) + 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++) { - 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 { - if (i < 0) continue; + Color = dirTextColor, + Icon = FontAwesomeIcon.Folder, + }; - var file = this.filteredFiles[i]; - var selected = this.selectedFileNames.Contains(file.FileName); - var needToBreak = false; + ImGui.PushStyleColor(ImGuiCol.Text, selected ? selectedTextColor : item.Color); - var dir = file.Type == FileStructType.Directory; - var item = !dir ? GetIcon(file.Ext) : new IconColorItem - { - Color = dirTextColor, - Icon = FontAwesomeIcon.Folder, - }; + ImGui.TableNextRow(); - ImGui.PushStyleColor(ImGuiCol.Text, selected ? selectedTextColor : item.Color); - - ImGui.TableNextRow(); - - if (ImGui.TableNextColumn()) - { - needToBreak = this.SelectableItem(file, selected, item.Icon); - } - - if (ImGui.TableNextColumn()) - { - ImGui.TextUnformatted(file.Ext); - } - - if (ImGui.TableNextColumn()) - { - if (file.Type == FileStructType.File) - { - ImGui.TextUnformatted(file.FormattedFileSize + " "); - } - else - { - ImGui.TextUnformatted(" "); - } - } - - if (ImGui.TableNextColumn()) - { - var sz = ImGui.CalcTextSize(file.FileModifiedDate); - ImGui.SetNextItemWidth(sz.X + Scaled(5)); - ImGui.TextUnformatted(file.FileModifiedDate + " "); - } - - ImGui.PopStyleColor(); - - if (needToBreak) break; + if (ImGui.TableNextColumn()) + { + needToBreak = this.SelectableItem(file, selected, item.Icon); } + + if (ImGui.TableNextColumn()) + { + ImGui.TextUnformatted(file.Ext); + } + + if (ImGui.TableNextColumn()) + { + if (file.Type == FileStructType.File) + { + ImGui.TextUnformatted(file.FormattedFileSize + " "); + } + else + { + ImGui.TextUnformatted(" "); + } + } + + if (ImGui.TableNextColumn()) + { + var sz = ImGui.CalcTextSize(file.FileModifiedDate); + ImGui.SetNextItemWidth(sz.X + Scaled(5)); + ImGui.TextUnformatted(file.FileModifiedDate + " "); + } + + ImGui.PopStyleColor(); + + if (needToBreak) break; } - - clipper.End(); - clipper.Destroy(); } + + clipper.End(); + clipper.Destroy(); } - - if (this.pathInputActivated) - { - if (ImGui.IsKeyReleased(ImGuiKey.Enter)) - { - if (Directory.Exists(this.pathInputBuffer)) this.SetPath(this.pathInputBuffer); - this.pathInputActivated = false; - } - - if (ImGui.IsKeyReleased(ImGuiKey.Escape)) - { - this.pathInputActivated = false; - } - } - - ImGui.EndTable(); } - if (this.pathClicked) + if (this.pathInputActivated) { - this.SetPath(this.currentPath); + if (ImGui.IsKeyReleased(ImGuiKey.Enter)) + { + if (Directory.Exists(this.pathInputBuffer)) this.SetPath(this.pathInputBuffer); + this.pathInputActivated = false; + } + + if (ImGui.IsKeyReleased(ImGuiKey.Escape)) + { + this.pathInputActivated = false; + } } - ImGui.EndChild(); + ImGui.EndTable(); } - private bool SelectableItem(FileStruct file, bool selected, FontAwesomeIcon icon) + if (this.pathClicked) { - const ImGuiSelectableFlags flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns; + this.SetPath(this.currentPath); + } - ImGui.PushFont(UiBuilder.IconFont); + ImGui.EndChild(); + } - ImGui.TextUnformatted(icon.ToIconString()); - ImGui.PopFont(); + private bool SelectableItem(FileStruct file, bool selected, FontAwesomeIcon icon) + { + const ImGuiSelectableFlags flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns; - ImGui.SameLine(Scaled(25f)); + ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Selectable(file.FileName, selected, flags)) + ImGui.TextUnformatted(icon.ToIconString()); + ImGui.PopFont(); + + ImGui.SameLine(Scaled(25f)); + + if (ImGui.Selectable(file.FileName, selected, flags)) + { + if (file.Type == FileStructType.Directory) { - if (file.Type == FileStructType.Directory) + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) { - if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) - { - this.pathClicked = this.SelectDirectory(file); - return true; - } - - if (this.IsDirectoryMode()) - { - this.SelectFileName(file); - } + this.pathClicked = this.SelectDirectory(file); + return true; } - else + + if (this.IsDirectoryMode()) { this.SelectFileName(file); - if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) - { - this.wantsToQuit = true; - this.isOk = true; - } - } - } - - return false; - } - - private bool SelectDirectory(FileStruct file) - { - var pathClick = false; - - if (file.FileName == "..") - { - if (this.pathDecomposition.Count > 1) - { - 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.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); @@ -569,76 +582,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)) @@ -650,201 +626,224 @@ 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.TextUnformatted("Directory Path :"); + } + else + { + ImGui.TextUnformatted("File Name :"); } - private void RemoveFileNameInSelection(string name) + ImGui.SameLine(); + + var width = ImGui.GetContentRegionAvail().X - Scaled(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 -= Scaled(150); } - private bool DrawFooter() + var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly); + + ImGui.SetNextItemWidth(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(); + + if (this.filters.Count > 0) { - var posY = ImGui.GetCursorPosY(); - - if (this.IsDirectoryMode()) - { - ImGui.TextUnformatted("Directory Path :"); - } - else - { - ImGui.TextUnformatted("File Name :"); - } - ImGui.SameLine(); + var needToApplyNewFilter = false; - var width = ImGui.GetContentRegionAvail().X - Scaled(100); - if (this.filters.Count > 0) + ImGui.SetNextItemWidth(Scaled(150f)); + if (ImGui.BeginCombo("##Filters", this.selectedFilter.Filter, ImGuiComboFlags.None)) { - width -= Scaled(150); - } - - var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly); - - ImGui.SetNextItemWidth(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(); - - if (this.filters.Count > 0) - { - ImGui.SameLine(); - var needToApplyNewFilter = false; - - ImGui.SetNextItemWidth(Scaled(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(); } - if (needToApplyNewFilter) - { - this.SetPath(this.currentPath); + ImGui.EndCombo(); + } + + 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; } - } - 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")) - { + // already exists, open dialog to confirm overwrite this.isOk = false; - res = true; + this.okResultToConfirm = true; } - this.footerHeight = ImGui.GetCursorPosY() - posY; + var name = $"The file Already Exists !##{this.title}{this.id}OverWriteDialog"; + var res = false; + var open = true; - if (this.wantsToQuit && this.isOk) + ImGui.OpenPopup(name); + if (ImGui.BeginPopupModal(name, ref open, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove)) { - res = true; + ImGui.TextUnformatted("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; } - 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; - } - - // 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.TextUnformatted("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 de42e9e9d..fe224be76 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs @@ -5,294 +5,293 @@ using System.Linq; using ImGuiNET; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// A file or folder picker. +/// +public partial class FileDialog { /// - /// A file or folder picker. + /// The flags used to draw the file picker window. /// - public partial class FileDialog - { - /// - /// The flags used to draw the file picker window. - /// #pragma warning disable SA1401 - public ImGuiWindowFlags WindowFlags; + public ImGuiWindowFlags WindowFlags; #pragma warning restore SA1401 - 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 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 bool visible; - private string currentPath; - private string fileNameBuffer = string.Empty; + 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 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 isModal = false; + private bool okResultToConfirm = false; + private bool isOk; + private bool wantsToQuit; - private bool createDirectoryMode = false; - private string createDirectoryBuffer = string.Empty; + private bool createDirectoryMode = false; + private string createDirectoryBuffer = string.Empty; - private string searchBuffer = string.Empty; + private string searchBuffer = string.Empty; - private string lastSelectedFileName = string.Empty; - private List selectedFileNames = new(); + private string lastSelectedFileName = string.Empty; + private List selectedFileNames = new(); - private float footerHeight = 0; + private float footerHeight = 0; - private string selectedSideBar = string.Empty; - private List drives = new(); - private List quickAccess = new(); + 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) + /// + /// 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) + { + this.id = id; + this.title = title; + this.flags = flags; + this.selectionCountMax = selectionCountMax; + this.isModal = isModal; + this.WindowFlags = ImGuiWindowFlags.NoNav; + if (!isModal) + this.WindowFlags |= ImGuiWindowFlags.NoScrollbar; + + this.currentPath = path; + this.defaultExtension = defaultExtension; + this.defaultFileName = defaultFileName; + + this.ParseFilters(filters); + this.SetSelectedFilterWithExt(this.defaultExtension); + this.SetDefaultFileName(); + this.SetPath(this.currentPath); + + this.SetupSideBar(); + } + + /// + /// Shows the dialog. + /// + public void Show() + { + this.visible = true; + } + + /// + /// Hides the dialog. + /// + public void Hide() + { + this.visible = false; + } + + /// + /// 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; + } + + /// + /// 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. + [Obsolete("Use GetResults() instead.", true)] + public string GetResult() + { + return string.Join(',', this.GetResults()); + } + + /// + /// Gets the result of the selection. + /// + /// The list of selected paths. + public List GetResults() + { + if (!this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly)) { - this.id = id; - this.title = title; - this.flags = flags; - this.selectionCountMax = selectionCountMax; - this.isModal = isModal; - this.WindowFlags = ImGuiWindowFlags.NoNav; - if (!isModal) - this.WindowFlags |= ImGuiWindowFlags.NoScrollbar; - - 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 new List { this.GetFilePathName() }; } - /// - /// Shows the dialog. - /// - public void Show() + if (this.IsDirectoryMode() && this.selectedFileNames.Count == 0) { - this.visible = true; + return new List { this.GetFilePathName() }; // current directory } - /// - /// Hides the dialog. - /// - public void Hide() - { - this.visible = false; - } + var fullPaths = this.selectedFileNames.Where(x => !string.IsNullOrEmpty(x)).Select(x => Path.Combine(this.currentPath, x)); + return fullPaths.ToList(); + } - /// - /// 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() + /// + /// Gets the current path of the dialog. + /// + /// The path of the directory which the dialog is current viewing. + public string GetCurrentPath() + { + if (this.IsDirectoryMode()) { - return this.isOk; - } - - /// - /// 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. - [Obsolete("Use GetResults() instead.", true)] - public string GetResult() - { - return string.Join(',', this.GetResults()); - } - - /// - /// Gets the result of the selection. - /// - /// The list of selected paths. - public List GetResults() - { - if (!this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly)) + // combine path file with directory input + var selectedDirectory = this.fileNameBuffer; + if (!string.IsNullOrEmpty(selectedDirectory) && selectedDirectory != ".") { - return new List { this.GetFilePathName() }; + return string.IsNullOrEmpty(this.currentPath) ? selectedDirectory : Path.Combine(this.currentPath, selectedDirectory); } - - if (this.IsDirectoryMode() && this.selectedFileNames.Count == 0) - { - return new List { this.GetFilePathName() }; // current directory - } - - var fullPaths = this.selectedFileNames.Where(x => !string.IsNullOrEmpty(x)).Select(x => Path.Combine(this.currentPath, x)); - return fullPaths.ToList(); } - /// - /// Gets the current path of the dialog. - /// - /// The path of the directory which the dialog is current viewing. - public string GetCurrentPath() + return this.currentPath; + } + + /// + /// Set or remove a quick access folder for the navigation panel. + /// + /// The displayed name of the folder. If this name already exists, it will be overwritten. + /// The new linked path. If this is empty, no link will be added and existing links will be removed. + /// The FontAwesomeIcon-ID of the icon displayed before the name. + /// An optional position at which to insert the new link. If the link is updated, having this less than zero will keep its position. + /// Otherwise, invalid indices will insert it at the end. + public void SetQuickAccess(string name, string path, FontAwesomeIcon icon, int position = -1) + { + var idx = this.quickAccess.FindIndex(q => q.Text.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + if (idx >= 0) { - if (this.IsDirectoryMode()) + if (position >= 0 || path.Length == 0) { - // 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; - } - - /// - /// Set or remove a quick access folder for the navigation panel. - /// - /// The displayed name of the folder. If this name already exists, it will be overwritten. - /// The new linked path. If this is empty, no link will be added and existing links will be removed. - /// The FontAwesomeIcon-ID of the icon displayed before the name. - /// An optional position at which to insert the new link. If the link is updated, having this less than zero will keep its position. - /// Otherwise, invalid indices will insert it at the end. - public void SetQuickAccess(string name, string path, FontAwesomeIcon icon, int position = -1) - { - var idx = this.quickAccess.FindIndex(q => q.Text.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - if (idx >= 0) - { - if (position >= 0 || path.Length == 0) - { - this.quickAccess.RemoveAt(idx); - } - else - { - this.quickAccess[idx] = new SideBarItem(name, path, icon); - return; - } - } - - if (path.Length == 0) return; - - if (position < 0 || position >= this.quickAccess.Count) - { - this.quickAccess.Add(new SideBarItem(name, path, icon)); + this.quickAccess.RemoveAt(idx); } else { - this.quickAccess.Insert(position, new SideBarItem(name, path, icon)); + this.quickAccess[idx] = new SideBarItem(name, path, icon); + return; } } - private string GetFilePathName() + if (path.Length == 0) return; + + if (position < 0 || position >= this.quickAccess.Count) { - var path = this.GetCurrentPath(); - var fileName = this.GetCurrentFileName(); + this.quickAccess.Add(new SideBarItem(name, path, icon)); + } + else + { + this.quickAccess.Insert(position, new SideBarItem(name, path, icon)); + } + } - if (!string.IsNullOrEmpty(fileName)) - { - return Path.Combine(path, fileName); - } + private string GetFilePathName() + { + var path = this.GetCurrentPath(); + var fileName = this.GetCurrentFileName(); - return path; + if (!string.IsNullOrEmpty(fileName)) + { + return Path.Combine(path, fileName); } - private string GetCurrentFileName() + return path; + } + + private string GetCurrentFileName() + { + if (this.IsDirectoryMode()) { - if (this.IsDirectoryMode()) - { - return string.Empty; - } + return string.Empty; + } - var result = this.fileNameBuffer; - - // a collection like {.cpp, .h}, so can't decide on an extension - if (this.selectedFilter.CollectionFilters is { 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[..lastPoint]; - } - - result += this.selectedFilter.Filter; - } + var result = this.fileNameBuffer; + // a collection like {.cpp, .h}, so can't decide on an extension + if (this.selectedFilter.CollectionFilters is { Count: > 0 }) + { 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[..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[..^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[..^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 eff871d30..7970279af 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs @@ -3,198 +3,197 @@ using System.Collections.Generic; using ImGuiNET; -namespace Dalamud.Interface.ImGuiFileDialog -{ - /// - /// A manager for the class. - /// - public class FileDialogManager - { -#pragma warning disable SA1401 - /// Additional quick access items for the side bar. - public readonly List<(string Name, string Path, FontAwesomeIcon Icon, int Position)> CustomSideBarItems = new(); +namespace Dalamud.Interface.ImGuiFileDialog; - /// Additional flags with which to draw the window. - public ImGuiWindowFlags AddedWindowFlags = ImGuiWindowFlags.None; +/// +/// A manager for the class. +/// +public class FileDialogManager +{ +#pragma warning disable SA1401 + /// Additional quick access items for the side bar. + public readonly List<(string Name, string Path, FontAwesomeIcon Icon, int Position)> CustomSideBarItems = new(); + + /// Additional flags with which to draw the window. + public ImGuiWindowFlags AddedWindowFlags = ImGuiWindowFlags.None; #pragma warning restore SA1401 - private FileDialog? dialog; - private Action? callback; - private Action>? multiCallback; - private string savedPath = "."; + private FileDialog? dialog; + private Action? callback; + private Action>? multiCallback; + private string savedPath = "."; - /// - /// 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. + /// + /// 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. - /// - /// The header title of the dialog. - /// The action to execute when the dialog is finished. - /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. - /// Whether the dialog should be a modal popup. - public void OpenFolderDialog(string title, Action callback, string? startPath, bool isModal = false) - { - this.SetDialog("OpenFolderDialog", title, string.Empty, startPath ?? this.savedPath, ".", string.Empty, 1, isModal, 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. + /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. + /// Whether the dialog should be a modal popup. + public void OpenFolderDialog(string title, Action callback, string? startPath, bool isModal = false) + { + this.SetDialog("OpenFolderDialog", title, string.Empty, startPath ?? this.savedPath, ".", string.Empty, 1, isModal, 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 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. - /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. - /// Whether the dialog should be a modal popup. - public void SaveFolderDialog(string title, string defaultFolderName, Action callback, string? startPath, bool isModal = false) - { - this.SetDialog("SaveFolderDialog", title, string.Empty, startPath ?? this.savedPath, defaultFolderName, string.Empty, 1, isModal, 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. + /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. + /// Whether the dialog should be a modal popup. + public void SaveFolderDialog(string title, string defaultFolderName, Action callback, string? startPath, bool isModal = false) + { + this.SetDialog("SaveFolderDialog", title, string.Empty, startPath ?? this.savedPath, defaultFolderName, string.Empty, 1, isModal, ImGuiFileDialogFlags.None, callback); + } - /// - /// Create a dialog which selects a single, 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 a single, 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 already existing files. - /// - /// The header title of the dialog. - /// Which files to show in the dialog. - /// The action to execute when the dialog is finished. - /// The maximum amount of files or directories which can be selected. Set to 0 for an infinite number. - /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. - /// Whether the dialog should be a modal popup. - public void OpenFileDialog( - string title, - string filters, - Action> callback, - int selectionCountMax, - string? startPath = null, - bool isModal = false) - { - this.SetDialog("OpenFileDialog", title, filters, startPath ?? this.savedPath, ".", string.Empty, selectionCountMax, isModal, ImGuiFileDialogFlags.SelectOnly, callback); - } + /// + /// Create a dialog which selects already existing files. + /// + /// The header title of the dialog. + /// Which files to show in the dialog. + /// The action to execute when the dialog is finished. + /// The maximum amount of files or directories which can be selected. Set to 0 for an infinite number. + /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. + /// Whether the dialog should be a modal popup. + public void OpenFileDialog( + string title, + string filters, + Action> callback, + int selectionCountMax, + string? startPath = null, + bool isModal = false) + { + this.SetDialog("OpenFileDialog", title, filters, startPath ?? this.savedPath, ".", string.Empty, selectionCountMax, isModal, 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); + } - /// - /// 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. - /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. - /// Whether the dialog should be a modal popup. - public void SaveFileDialog( - string title, - string filters, - string defaultFileName, - string defaultExtension, - Action callback, - string? startPath, - bool isModal = false) - { - this.SetDialog("SaveFileDialog", title, filters, startPath ?? this.savedPath, defaultFileName, defaultExtension, 1, isModal, 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. + /// The directory which the dialog should start inside of. The last path this manager was in is used if this is null. + /// Whether the dialog should be a modal popup. + public void SaveFileDialog( + string title, + string filters, + string defaultFileName, + string defaultExtension, + Action callback, + string? startPath, + bool isModal = false) + { + this.SetDialog("SaveFileDialog", title, filters, startPath ?? this.savedPath, defaultFileName, defaultExtension, 1, isModal, 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()) - { - var isOk = this.dialog.GetIsOk(); - var results = this.dialog.GetResults(); - this.callback?.Invoke(isOk, results.Count > 0 ? results[0] : string.Empty); - this.multiCallback?.Invoke(isOk, results); - 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; - this.multiCallback = null; - } - - private void SetDialog( - string id, - string title, - string filters, - string path, - string defaultFileName, - string defaultExtension, - int selectionCountMax, - bool isModal, - ImGuiFileDialogFlags flags, - Delegate 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()) { + var isOk = this.dialog.GetIsOk(); + var results = this.dialog.GetResults(); + this.callback?.Invoke(isOk, results.Count > 0 ? results[0] : string.Empty); + this.multiCallback?.Invoke(isOk, results); + this.savedPath = this.dialog.GetCurrentPath(); this.Reset(); - if (callback is Action> multi) - { - this.multiCallback = multi; - } - else - { - this.callback = callback as Action; - } - - this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags); - this.dialog.WindowFlags |= this.AddedWindowFlags; - foreach (var (name, location, icon, position) in this.CustomSideBarItems) - this.dialog.SetQuickAccess(name, location, icon, position); - this.dialog.Show(); } } + + /// + /// Removes the current dialog, if any. + /// + public void Reset() + { + this.dialog?.Hide(); + this.dialog = null; + this.callback = null; + this.multiCallback = null; + } + + private void SetDialog( + string id, + string title, + string filters, + string path, + string defaultFileName, + string defaultExtension, + int selectionCountMax, + bool isModal, + ImGuiFileDialogFlags flags, + Delegate callback) + { + this.Reset(); + if (callback is Action> multi) + { + this.multiCallback = multi; + } + else + { + this.callback = callback as Action; + } + + this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags); + this.dialog.WindowFlags |= this.AddedWindowFlags; + foreach (var (name, location, icon, position) in this.CustomSideBarItems) + this.dialog.SetQuickAccess(name, location, icon, position); + this.dialog.Show(); + } } diff --git a/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs b/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs index fd5ee2531..ea35f4c65 100644 --- a/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs +++ b/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs @@ -1,56 +1,55 @@ using System; -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 = 0x01, + /// + /// Confirm the selection when choosing a file which already exists. + /// + ConfirmOverwrite = 0x01, - /// - /// Only allow selection of files or folders which currently exist. - /// - SelectOnly = 0x02, + /// + /// Only allow selection of files or folders which currently exist. + /// + SelectOnly = 0x02, - /// - /// Hide files or folders which start with a period. - /// - DontShowHiddenFiles = 0x04, + /// + /// Hide files or folders which start with a period. + /// + DontShowHiddenFiles = 0x04, - /// - /// Disable the creation of new folders within the dialog. - /// - DisableCreateDirectoryButton = 0x08, + /// + /// Disable the creation of new folders within the dialog. + /// + DisableCreateDirectoryButton = 0x08, - /// - /// Hide the type column. - /// - HideColumnType = 0x10, + /// + /// Hide the type column. + /// + HideColumnType = 0x10, - /// - /// Hide the file size column. - /// - HideColumnSize = 0x20, + /// + /// Hide the file size column. + /// + HideColumnSize = 0x20, - /// - /// Hide the last modified date column. - /// - HideColumnDate = 0x40, + /// + /// Hide the last modified date column. + /// + HideColumnDate = 0x40, - /// - /// Hide the quick access sidebar. - /// - HideSideBar = 0x80, - } + /// + /// Hide the quick access sidebar. + /// + HideSideBar = 0x80, } diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs index 19704bd77..12fc1a284 100644 --- a/Dalamud/Interface/ImGuiHelpers.cs +++ b/Dalamud/Interface/ImGuiHelpers.cs @@ -8,401 +8,400 @@ using Dalamud.Game.ClientState.Keys; using ImGuiNET; using ImGuiScene; -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/Y parameter. + /// A scaled Vector2. + public static Vector2 ScaledVector2(float x) => new Vector2(x, x) * GlobalScale; + + /// + /// 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/Y parameter. - /// A scaled Vector2. - public static Vector2 ScaledVector2(float x) => new Vector2(x, x) * GlobalScale; - - /// - /// 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; + } - /// - /// Print out text that can be copied when clicked. - /// - /// The text to show. - /// The text to copy when clicked. - public static void ClickToCopyText(string text, string? textCopy = null) + /// + /// 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); + + /// + /// Print out text that can be copied when clicked. + /// + /// The text to show. + /// The text to copy when clicked. + public static void ClickToCopyText(string text, string? textCopy = null) + { + textCopy ??= text; + ImGui.Text($"{text}"); + if (ImGui.IsItemHovered()) { - textCopy ??= text; - ImGui.Text($"{text}"); - if (ImGui.IsItemHovered()) - { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); - } - - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + if (textCopy != text) ImGui.SetTooltip(textCopy); } - /// - /// Write unformatted text wrapped. - /// - /// The text to write. - public static void SafeTextWrapped(string text) => ImGui.TextWrapped(text.Replace("%", "%%")); + if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + } - /// - /// Fills missing glyphs in target font from source font, if both are not null. - /// - /// Source font. - /// Target font. - /// Whether to copy missing glyphs only. - /// Whether to call target.BuildLookupTable(). - /// Low codepoint range to copy. - /// High codepoing range to copy. - public static void CopyGlyphsAcrossFonts(ImFontPtr? source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable, int rangeLow = 32, int rangeHigh = 0xFFFE) + /// + /// Write unformatted text wrapped. + /// + /// The text to write. + public static void SafeTextWrapped(string text) => ImGui.TextWrapped(text.Replace("%", "%%")); + + /// + /// Fills missing glyphs in target font from source font, if both are not null. + /// + /// Source font. + /// Target font. + /// Whether to copy missing glyphs only. + /// Whether to call target.BuildLookupTable(). + /// Low codepoint range to copy. + /// High codepoing range to copy. + public static void CopyGlyphsAcrossFonts(ImFontPtr? source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable, int rangeLow = 32, int rangeHigh = 0xFFFE) + { + if (!source.HasValue || !target.HasValue) + return; + + var scale = target.Value!.FontSize / source.Value!.FontSize; + var addedCodepoints = new HashSet(); + unsafe { - if (!source.HasValue || !target.HasValue) - return; - - var scale = target.Value!.FontSize / source.Value!.FontSize; - var addedCodepoints = new HashSet(); - unsafe + var glyphs = (ImFontGlyphReal*)source.Value!.Glyphs.Data; + for (int j = 0, k = source.Value!.Glyphs.Size; j < k; j++) { - var glyphs = (ImFontGlyphReal*)source.Value!.Glyphs.Data; - for (int j = 0, k = source.Value!.Glyphs.Size; j < k; j++) + Debug.Assert(glyphs != null, nameof(glyphs) + " != null"); + + var glyph = &glyphs[j]; + if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh) + continue; + + var prevGlyphPtr = (ImFontGlyphReal*)target.Value!.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr; + if ((IntPtr)prevGlyphPtr == IntPtr.Zero) { - Debug.Assert(glyphs != null, nameof(glyphs) + " != null"); - - var glyph = &glyphs[j]; - if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh) - continue; - - var prevGlyphPtr = (ImFontGlyphReal*)target.Value!.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr; - if ((IntPtr)prevGlyphPtr == IntPtr.Zero) - { - addedCodepoints.Add(glyph->Codepoint); - target.Value!.AddGlyph( - target.Value!.ConfigData, - (ushort)glyph->Codepoint, - glyph->TextureIndex, - glyph->X0 * scale, - ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent, - glyph->X1 * scale, - ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent, - glyph->U0, - glyph->V0, - glyph->U1, - glyph->V1, - glyph->AdvanceX * scale); - } - else if (!missingOnly) - { - addedCodepoints.Add(glyph->Codepoint); - prevGlyphPtr->TextureIndex = glyph->TextureIndex; - prevGlyphPtr->X0 = glyph->X0 * scale; - prevGlyphPtr->Y0 = ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent; - prevGlyphPtr->X1 = glyph->X1 * scale; - prevGlyphPtr->Y1 = ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent; - prevGlyphPtr->U0 = glyph->U0; - prevGlyphPtr->V0 = glyph->V0; - prevGlyphPtr->U1 = glyph->U1; - prevGlyphPtr->V1 = glyph->V1; - prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale; - } + addedCodepoints.Add(glyph->Codepoint); + target.Value!.AddGlyph( + target.Value!.ConfigData, + (ushort)glyph->Codepoint, + glyph->TextureIndex, + glyph->X0 * scale, + ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent, + glyph->X1 * scale, + ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent, + glyph->U0, + glyph->V0, + glyph->U1, + glyph->V1, + glyph->AdvanceX * scale); } - - var kernPairs = source.Value!.KerningPairs; - for (int j = 0, k = kernPairs.Size; j < k; j++) + else if (!missingOnly) { - if (!addedCodepoints.Contains(kernPairs[j].Left)) - continue; - if (!addedCodepoints.Contains(kernPairs[j].Right)) - continue; - target.Value.AddKerningPair(kernPairs[j].Left, kernPairs[j].Right, kernPairs[j].AdvanceXAdjustment); + addedCodepoints.Add(glyph->Codepoint); + prevGlyphPtr->TextureIndex = glyph->TextureIndex; + prevGlyphPtr->X0 = glyph->X0 * scale; + prevGlyphPtr->Y0 = ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent; + prevGlyphPtr->X1 = glyph->X1 * scale; + prevGlyphPtr->Y1 = ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent; + prevGlyphPtr->U0 = glyph->U0; + prevGlyphPtr->V0 = glyph->V0; + prevGlyphPtr->U1 = glyph->U1; + prevGlyphPtr->V1 = glyph->V1; + prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale; } } - if (rebuildLookupTable && target.Value!.Glyphs.Size > 0) - target.Value!.BuildLookupTable(); - } - - /// - /// Map a VirtualKey keycode to an ImGuiKey enum value. - /// - /// The VirtualKey value to retrieve the ImGuiKey counterpart for. - /// The ImGuiKey that corresponds to this VirtualKey, or ImGuiKey.None otherwise. - public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key) - { - return ImGui_Input_Impl_Direct.VirtualKeyToImGuiKey((int)key); - } - - /// - /// Map an ImGuiKey enum value to a VirtualKey code. - /// - /// The ImGuiKey value to retrieve the VirtualKey counterpart for. - /// The VirtualKey that corresponds to this ImGuiKey, or VirtualKey.NO_KEY otherwise. - public static VirtualKey ImGuiKeyToVirtualKey(ImGuiKey key) - { - return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key); - } - - /// - /// Show centered text. - /// - /// Text to show. - public static void CenteredText(string text) - { - CenterCursorForText(text); - ImGui.TextUnformatted(text); - } - - /// - /// Center the ImGui cursor for a certain text. - /// - /// The text to center for. - public static void CenterCursorForText(string text) - { - var textWidth = ImGui.CalcTextSize(text).X; - CenterCursorFor((int)textWidth); - } - - /// - /// Center the ImGui cursor for an item with a certain width. - /// - /// The width to center for. - public static void CenterCursorFor(int itemWidth) - { - var window = (int)ImGui.GetWindowWidth(); - ImGui.SetCursorPosX((window / 2) - (itemWidth / 2)); - } - - /// - /// Get data needed for each new frame. - /// - internal static void NewFrame() - { - GlobalScale = ImGui.GetIO().FontGlobalScale; - } - - /// - /// ImFontGlyph the correct version. - /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")] - public struct ImFontGlyphReal - { - public uint ColoredVisibleTextureIndexCodepoint; - public float AdvanceX; - public float X0; - public float Y0; - public float X1; - public float Y1; - public float U0; - public float V0; - public float U1; - public float V1; - - private const uint ColoredMask /*****/ = 0b_00000000_00000000_00000000_00000001u; - private const uint VisibleMask /*****/ = 0b_00000000_00000000_00000000_00000010u; - private const uint TextureMask /*****/ = 0b_00000000_00000000_00000111_11111100u; - private const uint CodepointMask /***/ = 0b_11111111_11111111_11111000_00000000u; - - private const int ColoredShift = 0; - private const int VisibleShift = 1; - private const int TextureShift = 2; - private const int CodepointShift = 11; - - public bool Colored + var kernPairs = source.Value!.KerningPairs; + for (int j = 0, k = kernPairs.Size; j < k; j++) { - get => (int)((this.ColoredVisibleTextureIndexCodepoint & ColoredMask) >> ColoredShift) != 0; - set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~ColoredMask) | (value ? 1u << ColoredShift : 0u); - } - - public bool Visible - { - get => (int)((this.ColoredVisibleTextureIndexCodepoint & VisibleMask) >> VisibleShift) != 0; - set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~VisibleMask) | (value ? 1u << VisibleShift : 0u); - } - - public int TextureIndex - { - get => (int)(this.ColoredVisibleTextureIndexCodepoint & TextureMask) >> TextureShift; - set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~TextureMask) | ((uint)value << TextureShift); - } - - public int Codepoint - { - get => (int)(this.ColoredVisibleTextureIndexCodepoint & CodepointMask) >> CodepointShift; - set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~CodepointMask) | ((uint)value << CodepointShift); + if (!addedCodepoints.Contains(kernPairs[j].Left)) + continue; + if (!addedCodepoints.Contains(kernPairs[j].Right)) + continue; + target.Value.AddKerningPair(kernPairs[j].Left, kernPairs[j].Right, kernPairs[j].AdvanceXAdjustment); } } - /// - /// ImFontGlyphHotData the correct version. - /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")] - public struct ImFontGlyphHotDataReal + if (rebuildLookupTable && target.Value!.Glyphs.Size > 0) + target.Value!.BuildLookupTable(); + } + + /// + /// Map a VirtualKey keycode to an ImGuiKey enum value. + /// + /// The VirtualKey value to retrieve the ImGuiKey counterpart for. + /// The ImGuiKey that corresponds to this VirtualKey, or ImGuiKey.None otherwise. + public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key) + { + return ImGui_Input_Impl_Direct.VirtualKeyToImGuiKey((int)key); + } + + /// + /// Map an ImGuiKey enum value to a VirtualKey code. + /// + /// The ImGuiKey value to retrieve the VirtualKey counterpart for. + /// The VirtualKey that corresponds to this ImGuiKey, or VirtualKey.NO_KEY otherwise. + public static VirtualKey ImGuiKeyToVirtualKey(ImGuiKey key) + { + return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key); + } + + /// + /// Show centered text. + /// + /// Text to show. + public static void CenteredText(string text) + { + CenterCursorForText(text); + ImGui.TextUnformatted(text); + } + + /// + /// Center the ImGui cursor for a certain text. + /// + /// The text to center for. + public static void CenterCursorForText(string text) + { + var textWidth = ImGui.CalcTextSize(text).X; + CenterCursorFor((int)textWidth); + } + + /// + /// Center the ImGui cursor for an item with a certain width. + /// + /// The width to center for. + public static void CenterCursorFor(int itemWidth) + { + var window = (int)ImGui.GetWindowWidth(); + ImGui.SetCursorPosX((window / 2) - (itemWidth / 2)); + } + + /// + /// Get data needed for each new frame. + /// + internal static void NewFrame() + { + GlobalScale = ImGui.GetIO().FontGlobalScale; + } + + /// + /// ImFontGlyph the correct version. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")] + public struct ImFontGlyphReal + { + public uint ColoredVisibleTextureIndexCodepoint; + public float AdvanceX; + public float X0; + public float Y0; + public float X1; + public float Y1; + public float U0; + public float V0; + public float U1; + public float V1; + + private const uint ColoredMask /*****/ = 0b_00000000_00000000_00000000_00000001u; + private const uint VisibleMask /*****/ = 0b_00000000_00000000_00000000_00000010u; + private const uint TextureMask /*****/ = 0b_00000000_00000000_00000111_11111100u; + private const uint CodepointMask /***/ = 0b_11111111_11111111_11111000_00000000u; + + private const int ColoredShift = 0; + private const int VisibleShift = 1; + private const int TextureShift = 2; + private const int CodepointShift = 11; + + public bool Colored { - public float AdvanceX; - public float OccupiedWidth; - public uint KerningPairInfo; - - private const uint UseBisectMask /***/ = 0b_00000000_00000000_00000000_00000001u; - private const uint OffsetMask /******/ = 0b_00000000_00001111_11111111_11111110u; - private const uint CountMask /*******/ = 0b_11111111_11110000_00000111_11111100u; - - private const int UseBisectShift = 0; - private const int OffsetShift = 1; - private const int CountShift = 20; - - public bool UseBisect - { - get => (int)((this.KerningPairInfo & UseBisectMask) >> UseBisectShift) != 0; - set => this.KerningPairInfo = (this.KerningPairInfo & ~UseBisectMask) | (value ? 1u << UseBisectShift : 0u); - } - - public bool Offset - { - get => (int)((this.KerningPairInfo & OffsetMask) >> OffsetShift) != 0; - set => this.KerningPairInfo = (this.KerningPairInfo & ~OffsetMask) | (value ? 1u << OffsetShift : 0u); - } - - public int Count - { - get => (int)(this.KerningPairInfo & CountMask) >> CountShift; - set => this.KerningPairInfo = (this.KerningPairInfo & ~CountMask) | ((uint)value << CountShift); - } + get => (int)((this.ColoredVisibleTextureIndexCodepoint & ColoredMask) >> ColoredShift) != 0; + set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~ColoredMask) | (value ? 1u << ColoredShift : 0u); } - /// - /// ImFontAtlasCustomRect the correct version. - /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")] - public unsafe struct ImFontAtlasCustomRectReal + public bool Visible { - public ushort Width; - public ushort Height; - public ushort X; - public ushort Y; - public uint TextureIndexAndGlyphId; - public float GlyphAdvanceX; - public Vector2 GlyphOffset; - public ImFont* Font; + get => (int)((this.ColoredVisibleTextureIndexCodepoint & VisibleMask) >> VisibleShift) != 0; + set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~VisibleMask) | (value ? 1u << VisibleShift : 0u); + } - private const uint TextureIndexMask /***/ = 0b_00000000_00000000_00000111_11111100u; - private const uint GlyphIDMask /********/ = 0b_11111111_11111111_11111000_00000000u; + public int TextureIndex + { + get => (int)(this.ColoredVisibleTextureIndexCodepoint & TextureMask) >> TextureShift; + set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~TextureMask) | ((uint)value << TextureShift); + } - private const int TextureIndexShift = 2; - private const int GlyphIDShift = 11; + public int Codepoint + { + get => (int)(this.ColoredVisibleTextureIndexCodepoint & CodepointMask) >> CodepointShift; + set => this.ColoredVisibleTextureIndexCodepoint = (this.ColoredVisibleTextureIndexCodepoint & ~CodepointMask) | ((uint)value << CodepointShift); + } + } - public int TextureIndex - { - get => (int)(this.TextureIndexAndGlyphId & TextureIndexMask) >> TextureIndexShift; - set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~TextureIndexMask) | ((uint)value << TextureIndexShift); - } + /// + /// ImFontGlyphHotData the correct version. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")] + public struct ImFontGlyphHotDataReal + { + public float AdvanceX; + public float OccupiedWidth; + public uint KerningPairInfo; - public int GlyphId - { - get => (int)(this.TextureIndexAndGlyphId & GlyphIDMask) >> GlyphIDShift; - set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~GlyphIDMask) | ((uint)value << GlyphIDShift); - } + private const uint UseBisectMask /***/ = 0b_00000000_00000000_00000000_00000001u; + private const uint OffsetMask /******/ = 0b_00000000_00001111_11111111_11111110u; + private const uint CountMask /*******/ = 0b_11111111_11110000_00000111_11111100u; + + private const int UseBisectShift = 0; + private const int OffsetShift = 1; + private const int CountShift = 20; + + public bool UseBisect + { + get => (int)((this.KerningPairInfo & UseBisectMask) >> UseBisectShift) != 0; + set => this.KerningPairInfo = (this.KerningPairInfo & ~UseBisectMask) | (value ? 1u << UseBisectShift : 0u); + } + + public bool Offset + { + get => (int)((this.KerningPairInfo & OffsetMask) >> OffsetShift) != 0; + set => this.KerningPairInfo = (this.KerningPairInfo & ~OffsetMask) | (value ? 1u << OffsetShift : 0u); + } + + public int Count + { + get => (int)(this.KerningPairInfo & CountMask) >> CountShift; + set => this.KerningPairInfo = (this.KerningPairInfo & ~CountMask) | ((uint)value << CountShift); + } + } + + /// + /// ImFontAtlasCustomRect the correct version. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")] + public unsafe struct ImFontAtlasCustomRectReal + { + public ushort Width; + public ushort Height; + public ushort X; + public ushort Y; + public uint TextureIndexAndGlyphId; + public float GlyphAdvanceX; + public Vector2 GlyphOffset; + public ImFont* Font; + + private const uint TextureIndexMask /***/ = 0b_00000000_00000000_00000111_11111100u; + private const uint GlyphIDMask /********/ = 0b_11111111_11111111_11111000_00000000u; + + private const int TextureIndexShift = 2; + private const int GlyphIDShift = 11; + + public int TextureIndex + { + get => (int)(this.TextureIndexAndGlyphId & TextureIndexMask) >> TextureIndexShift; + set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~TextureIndexMask) | ((uint)value << TextureIndexShift); + } + + public int GlyphId + { + get => (int)(this.TextureIndexAndGlyphId & GlyphIDMask) >> GlyphIDShift; + set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~GlyphIDMask) | ((uint)value << GlyphIDShift); } } } diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index 49ce45a95..35e216eef 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -13,378 +13,377 @@ using Dalamud.Plugin.Internal; using Dalamud.Utility; using Serilog; -namespace Dalamud.Interface.Internal +namespace Dalamud.Interface.Internal; + +/// +/// Class handling Dalamud core commands. +/// +[ServiceManager.EarlyLoadedService] +internal class DalamudCommands : IServiceType { - /// - /// Class handling Dalamud core commands. - /// - [ServiceManager.EarlyLoadedService] - internal class DalamudCommands : IServiceType + [ServiceManager.ServiceConstructor] + private DalamudCommands(CommandManager commandManager) { - [ServiceManager.ServiceConstructor] - private DalamudCommands(CommandManager commandManager) + commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) { - commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) - { - HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), - ShowInHelp = false, - }); + HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), + ShowInHelp = false, + }); - commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand) - { - HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."), - }); + 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("/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("/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("/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("/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("/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("/xldev", new CommandInfo(this.OnDebugDrawDevMenu) + { + HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"), + ShowInHelp = false, + }); - commandManager.AddHandler("/xlstats", new CommandInfo(this.OnTogglePluginStats) - { - HelpMessage = Loc.Localize("DalamudPluginStats", "Draw plugin statistics window"), - ShowInHelp = false, - }); + commandManager.AddHandler("/xlstats", new CommandInfo(this.OnTogglePluginStats) + { + HelpMessage = Loc.Localize("DalamudPluginStats", "Draw plugin statistics window"), + ShowInHelp = false, + }); - commandManager.AddHandler("/xlbranch", new CommandInfo(this.OnToggleBranchSwitcher) - { - HelpMessage = Loc.Localize("DalamudBranchSwitcher", "Draw branch switcher"), - ShowInHelp = false, - }); + commandManager.AddHandler("/xlbranch", new CommandInfo(this.OnToggleBranchSwitcher) + { + HelpMessage = Loc.Localize("DalamudBranchSwitcher", "Draw branch switcher"), + 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("/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("/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("/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("/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("/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 Dalamud and plugins that support it. Available languages: ") + - Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code), - }); + commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand) + { + HelpMessage = + Loc.Localize( + "DalamudLanguageHelp", + "Set the language for Dalamud 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("/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("/xlversion", new CommandInfo(this.OnVersionInfoCommand) - { - HelpMessage = "Dalamud version info", - }); + commandManager.AddHandler("/xlversion", new CommandInfo(this.OnVersionInfoCommand) + { + HelpMessage = "Dalamud version info", + }); - commandManager.AddHandler("/xlui", new CommandInfo(this.OnUiCommand) - { - HelpMessage = Loc.Localize( - "DalamudUiModeHelp", - "Toggle Dalamud UI display modes. Native UI modifications may also be affected by this, but that depends on the plugin."), - }); + commandManager.AddHandler("/xlui", new CommandInfo(this.OnUiCommand) + { + HelpMessage = Loc.Localize( + "DalamudUiModeHelp", + "Toggle Dalamud UI display modes. Native UI modifications may also be affected by this, but that depends on the plugin."), + }); - commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand) - { - HelpMessage = "ImGui DEBUG", - ShowInHelp = false, - }); + commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand) + { + HelpMessage = "ImGui DEBUG", + ShowInHelp = false, + }); + } + + 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) + { + if (!cmd.Value.ShowInHelp && !showDebug) + continue; + + chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}"); + } + } + + 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; } - private void OnUnloadCommand(string command, string arguments) + 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) { - Service.Get().Print("Unloading..."); - Service.Get().Unload(); + chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences.")); + return; } - private void OnHelpCommand(string command, string arguments) + 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)) { - 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) - { - if (!cmd.Value.ShowInHelp && !showDebug) - continue; - - chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}"); - } + chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link...")); + return; } - private void OnBadWordsAddCommand(string command, string arguments) + chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink)); + Process.Start(new ProcessStartInfo(chatHandlers.LastLink) { - var chatGui = Service.Get(); - var configuration = Service.Get(); + UseShellExecute = true, + }); + } - configuration.BadWords ??= new List(); + private void OnBgmSetCommand(string command, string arguments) + { + var gameGui = Service.Get(); - if (string.IsNullOrEmpty(arguments)) - { - chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute.")); - return; - } + if (ushort.TryParse(arguments, out var value)) + { + gameGui.SetBgm(value); + } + else + { + // Revert to the original BGM by specifying an invalid one + gameGui.SetBgm(9999); + } + } - configuration.BadWords.Add(arguments); + private void OnDebugDrawDevMenu(string command, string arguments) + { + Service.Get().ToggleDevMenu(); + } - configuration.Save(); + private void OnTogglePluginStats(string command, string arguments) + { + Service.Get().TogglePluginStatsWindow(); + } - chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments)); + private void OnToggleBranchSwitcher(string command, string arguments) + { + Service.Get().ToggleBranchSwitcher(); + } + + 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 OnVersionInfoCommand(string command, string arguments) + { + var chatGui = Service.Get(); + + chatGui.Print(new SeStringBuilder() + .AddItalics("Dalamud:") + .AddText($" D{Util.AssemblyVersion}({Util.GetGitHash()}") + .Build()); + + chatGui.Print(new SeStringBuilder() + .AddItalics("FFXIVCS:") + .AddText($" {Util.GetGitHashClientStructs()}") + .Build()); + } + + 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")); } - private void OnBadWordsListCommand(string command, string arguments) + configuration.Save(); + } + + private void OnOpenSettingsCommand(string command, string arguments) + { + Service.Get().ToggleSettingsWindow(); + } + + private void OnUiCommand(string command, string arguments) + { + var im = Service.Get(); + + im.IsDispatchingEvents = arguments switch { - var chatGui = Service.Get(); - var configuration = Service.Get(); + "show" => true, + "hide" => false, + _ => !im.IsDispatchingEvents, + }; - configuration.BadWords ??= new List(); + var pm = Service.Get(); - 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) + foreach (var plugin in pm.InstalledPlugins) { - 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)) + if (im.IsDispatchingEvents) { - 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); + plugin.DalamudInterface?.UiBuilder.NotifyShowUi(); } 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 OnTogglePluginStats(string command, string arguments) - { - Service.Get().TogglePluginStatsWindow(); - } - - private void OnToggleBranchSwitcher(string command, string arguments) - { - Service.Get().ToggleBranchSwitcher(); - } - - 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 OnVersionInfoCommand(string command, string arguments) - { - var chatGui = Service.Get(); - - chatGui.Print(new SeStringBuilder() - .AddItalics("Dalamud:") - .AddText($" D{Util.AssemblyVersion}({Util.GetGitHash()}") - .Build()); - - chatGui.Print(new SeStringBuilder() - .AddItalics("FFXIVCS:") - .AddText($" {Util.GetGitHashClientStructs()}") - .Build()); - } - - 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(); - } - - private void OnUiCommand(string command, string arguments) - { - var im = Service.Get(); - - im.IsDispatchingEvents = arguments switch - { - "show" => true, - "hide" => false, - _ => !im.IsDispatchingEvents, - }; - - var pm = Service.Get(); - - foreach (var plugin in pm.InstalledPlugins) - { - if (im.IsDispatchingEvents) - { - plugin.DalamudInterface?.UiBuilder.NotifyShowUi(); - } - else - { - plugin.DalamudInterface?.UiBuilder.NotifyHideUi(); - } + plugin.DalamudInterface?.UiBuilder.NotifyHideUi(); } } } diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index a8808e54f..e6048f2ec 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -32,365 +32,365 @@ using ImPlotNET; 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. +/// +[ServiceManager.EarlyLoadedService] +internal class DalamudInterface : IDisposable, IServiceType { - /// - /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. - /// - [ServiceManager.EarlyLoadedService] - internal class DalamudInterface : IDisposable, IServiceType - { - 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 TitleScreenMenuWindow titleScreenMenuWindow; - private readonly ProfilerWindow profilerWindow; - private readonly BranchSwitcherWindow branchSwitcherWindow; + 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 TitleScreenMenuWindow titleScreenMenuWindow; + private readonly ProfilerWindow profilerWindow; + private readonly BranchSwitcherWindow branchSwitcherWindow; - private readonly TextureWrap logoTexture; - private readonly TextureWrap tsmLogoTexture; + private readonly TextureWrap logoTexture; + private readonly TextureWrap tsmLogoTexture; #if DEBUG private bool isImGuiDrawDevMenu = true; #else - private bool isImGuiDrawDevMenu = false; + private bool isImGuiDrawDevMenu = false; #endif #if BOOT_AGING private bool signaledBoot = false; #endif - private bool isImGuiDrawDemoWindow = false; - private bool isImPlotDrawDemoWindow = false; - private bool isImGuiTestWindowsInMonospace = false; - private bool isImGuiDrawMetricsWindow = false; + private bool isImGuiDrawDemoWindow = false; + private bool isImPlotDrawDemoWindow = false; + private bool isImGuiTestWindowsInMonospace = false; + private bool isImGuiDrawMetricsWindow = false; - [ServiceManager.ServiceConstructor] - private DalamudInterface( - Dalamud dalamud, - DalamudConfiguration configuration, - InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene, - PluginImageCache pluginImageCache) + [ServiceManager.ServiceConstructor] + private DalamudInterface( + Dalamud dalamud, + DalamudConfiguration configuration, + InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene, + PluginImageCache pluginImageCache) + { + var interfaceManager = interfaceManagerWithScene.Manager; + 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(pluginImageCache) { IsOpen = false }; + this.settingsWindow = new SettingsWindow() { IsOpen = false }; + this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; + this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; + this.titleScreenMenuWindow = new TitleScreenMenuWindow() { IsOpen = false }; + this.profilerWindow = new ProfilerWindow() { IsOpen = false }; + this.branchSwitcherWindow = new BranchSwitcherWindow() { 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); + this.WindowSystem.AddWindow(this.titleScreenMenuWindow); + this.WindowSystem.AddWindow(this.profilerWindow); + this.WindowSystem.AddWindow(this.branchSwitcherWindow); + + ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; + this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; + + interfaceManager.Draw += this.OnDraw; + + var logoTex = + interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); + var tsmLogoTex = + interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png")); + + if (logoTex == null || tsmLogoTex == null) { - var interfaceManager = interfaceManagerWithScene.Manager; - 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(pluginImageCache) { IsOpen = false }; - this.settingsWindow = new SettingsWindow() { IsOpen = false }; - this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; - this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; - this.titleScreenMenuWindow = new TitleScreenMenuWindow() { IsOpen = false }; - this.profilerWindow = new ProfilerWindow() { IsOpen = false }; - this.branchSwitcherWindow = new BranchSwitcherWindow() { 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); - this.WindowSystem.AddWindow(this.titleScreenMenuWindow); - this.WindowSystem.AddWindow(this.profilerWindow); - this.WindowSystem.AddWindow(this.branchSwitcherWindow); - - ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; - this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; - - interfaceManager.Draw += this.OnDraw; - - var logoTex = - interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); - var tsmLogoTex = - interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png")); - - if (logoTex == null || tsmLogoTex == null) - { - throw new Exception("Failed to load logo textures"); - } - - this.logoTexture = logoTex; - this.tsmLogoTexture = tsmLogoTex; - - var tsm = Service.Get(); - tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), this.tsmLogoTexture, () => this.pluginWindow.IsOpen = true); - tsm.AddEntryCore(Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), this.tsmLogoTexture, () => this.settingsWindow.IsOpen = true); - - if (!configuration.DalamudBetaKind.IsNullOrEmpty()) - { - tsm.AddEntryCore(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), this.tsmLogoTexture, () => this.isImGuiDrawDevMenu = true); - } + throw new Exception("Failed to load logo textures"); } - /// - /// Gets the number of frames since Dalamud has loaded. - /// - public ulong FrameCount { get; private set; } + this.logoTexture = logoTex; + this.tsmLogoTexture = tsmLogoTex; - /// - /// Gets the controlling all Dalamud-internal windows. - /// - public WindowSystem WindowSystem { get; init; } + var tsm = Service.Get(); + tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), this.tsmLogoTexture, () => this.pluginWindow.IsOpen = true); + tsm.AddEntryCore(Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), this.tsmLogoTexture, () => this.settingsWindow.IsOpen = true); - /// - /// Gets or sets a value indicating whether the /xldev menu is open. - /// - public bool IsDevMenuOpen + if (!configuration.DalamudBetaKind.IsNullOrEmpty()) { - get => this.isImGuiDrawDevMenu; - set => this.isImGuiDrawDevMenu = value; + tsm.AddEntryCore(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), this.tsmLogoTexture, () => this.isImGuiDrawDevMenu = true); } + } - /// - public void Dispose() + /// + /// Gets the number of frames since Dalamud has loaded. + /// + public ulong FrameCount { get; private set; } + + /// + /// 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.changelogWindow.Dispose(); + this.creditsWindow.Dispose(); + this.consoleWindow.Dispose(); + this.pluginWindow.Dispose(); + this.titleScreenMenuWindow.Dispose(); + + this.logoTexture.Dispose(); + this.tsmLogoTexture.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) { - Service.Get().Draw -= this.OnDraw; - - this.WindowSystem.RemoveAllWindows(); - - this.changelogWindow.Dispose(); - this.creditsWindow.Dispose(); - this.consoleWindow.Dispose(); - this.pluginWindow.Dispose(); - this.titleScreenMenuWindow.Dispose(); - - this.logoTexture.Dispose(); - this.tsmLogoTexture.Dispose(); + this.dataWindow.SetDataKind(dataKind); } + } - #region Open + /// + /// Opens the dev menu bar. + /// + public void OpenDevMenu() => this.isImGuiDrawDevMenu = true; - /// - /// Opens the . - /// - public void OpenChangelogWindow() => this.changelogWindow.IsOpen = true; + /// + /// Opens the . + /// + public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; - /// - /// Opens the . - /// - public void OpenColorsDemoWindow() => this.colorDemoWindow.IsOpen = true; + /// + /// Opens the . + /// + public void OpenImeWindow() => this.imeWindow.IsOpen = true; - /// - /// Opens the . - /// - public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true; + /// + /// Opens the . + /// + public void OpenLogWindow() => this.consoleWindow.IsOpen = true; - /// - /// Opens the . - /// - public void OpenCreditsWindow() => this.creditsWindow.IsOpen = true; + /// + /// Opens the . + /// + public void OpenPluginStats() => this.pluginStatWindow.IsOpen = true; - /// - /// Opens the . - /// - /// The data kind to switch to after opening. - public void OpenDataWindow(string? dataKind = null) + /// + /// Opens the . + /// + public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; + + /// + /// Opens the on the plugin changelogs. + /// + public void OpenPluginInstallerPluginChangelogs() => this.pluginWindow.OpenPluginChangelogs(); + + /// + /// 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; + + /// + /// Opens the . + /// + public void OpenProfiler() => this.profilerWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenBranchSwitcher() => this.branchSwitcherWindow.IsOpen = true; + + #endregion + + #region Close + + /// + /// Closes the . + /// + public void CloseImeWindow() => this.imeWindow.IsOpen = false; + + /// + /// Closes the . + /// + public void CloseGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.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) { - this.dataWindow.IsOpen = true; - if (dataKind != null && this.dataWindow.IsOpen) - { - this.dataWindow.SetDataKind(dataKind); - } + this.dataWindow.SetDataKind(dataKind); } + } - /// - /// Opens the dev menu bar. - /// - public void OpenDevMenu() => this.isImGuiDrawDevMenu = true; + /// + /// Toggles the dev menu bar. + /// + public void ToggleDevMenu() => this.isImGuiDrawDevMenu ^= true; - /// - /// Opens the . - /// - public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenImeWindow() => this.imeWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleIMEWindow() => this.imeWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenLogWindow() => this.consoleWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleLogWindow() => this.consoleWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenPluginStats() => this.pluginStatWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void TogglePluginStatsWindow() => this.pluginStatWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void TogglePluginInstallerWindow() => this.pluginWindow.Toggle(); - /// - /// Opens the on the plugin changelogs. - /// - public void OpenPluginInstallerPluginChangelogs() => this.pluginWindow.OpenPluginChangelogs(); + /// + /// Toggles the . + /// + public void ToggleSettingsWindow() => this.settingsWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenSettings() => this.settingsWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenSelfTest() => this.selfTestWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleProfilerWindow() => this.profilerWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenProfiler() => this.profilerWindow.IsOpen = true; + /// + /// Toggles the . + /// + public void ToggleBranchSwitcher() => this.branchSwitcherWindow.Toggle(); - /// - /// Opens the . - /// - public void OpenBranchSwitcher() => this.branchSwitcherWindow.IsOpen = true; + #endregion - #endregion - - #region Close - - /// - /// Closes the . - /// - public void CloseImeWindow() => this.imeWindow.IsOpen = false; - - /// - /// Closes the . - /// - public void CloseGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.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) - { - this.dataWindow.SetDataKind(dataKind); - } - } - - /// - /// 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(); - - /// - /// Toggles the . - /// - public void ToggleProfilerWindow() => this.profilerWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleBranchSwitcher() => this.branchSwitcherWindow.Toggle(); - - #endregion - - private void OnDraw() - { - this.FrameCount++; + private void OnDraw() + { + this.FrameCount++; #if BOOT_AGING if (this.frameCount > 500 && !this.signaledBoot) @@ -405,456 +405,455 @@ namespace Dalamud.Interface.Internal } #endif - try + try + { + this.DrawHiddenDevMenuOpener(); + this.DrawDevMenu(); + + if (Service.Get().GameUiHidden) + return; + + this.WindowSystem.Draw(); + + if (this.isImGuiTestWindowsInMonospace) + ImGui.PushFont(InterfaceManager.MonoFont); + + if (this.isImGuiDrawDemoWindow) + ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow); + + if (this.isImPlotDrawDemoWindow) + ImPlot.ShowDemoWindow(ref this.isImPlotDrawDemoWindow); + + if (this.isImGuiDrawMetricsWindow) + ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow); + + if (this.isImGuiTestWindowsInMonospace) + ImGui.PopFont(); + + // 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.DrawHiddenDevMenuOpener(); - this.DrawDevMenu(); - - if (Service.Get().GameUiHidden) - return; - - this.WindowSystem.Draw(); - - if (this.isImGuiTestWindowsInMonospace) - ImGui.PushFont(InterfaceManager.MonoFont); - - if (this.isImGuiDrawDemoWindow) - ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow); - - if (this.isImPlotDrawDemoWindow) - ImPlot.ShowDemoWindow(ref this.isImPlotDrawDemoWindow); - - if (this.isImGuiDrawMetricsWindow) - ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow); - - if (this.isImGuiTestWindowsInMonospace) - ImGui.PopFont(); - - // 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"); + ImGui.SetWindowFocus(null); } } - - private void DrawHiddenDevMenuOpener() + catch (Exception ex) { - var condition = Service.Get(); + PluginLog.Error(ex, "Error during OnDraw"); + } + } - if (!this.isImGuiDrawDevMenu && !condition.Any()) + 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.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)); + + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 0); + + var windowPos = ImGui.GetMainViewport().Pos + new Vector2(20); + ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always); + ImGui.SetNextWindowBgAlpha(1); + + if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) { - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - 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)); + ImGui.SetNextItemWidth(40); + if (ImGui.Button("###devMenuOpener", new Vector2(20, 20))) + this.isImGuiDrawDevMenu = true; - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 0); + ImGui.End(); + } - var windowPos = ImGui.GetMainViewport().Pos + new Vector2(20); + if (EnvironmentConfiguration.DalamudForceMinHook) + { ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always); ImGui.SetNextWindowBgAlpha(1); - if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) + if (ImGui.Begin( + "Disclaimer", + ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | + ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoMouseInputs | + ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) { - ImGui.SetNextItemWidth(40); - if (ImGui.Button("###devMenuOpener", new Vector2(20, 20))) - this.isImGuiDrawDevMenu = true; - - ImGui.End(); + ImGui.TextColored(ImGuiColors.DalamudRed, "Is force MinHook!"); } - if (EnvironmentConfiguration.DalamudForceMinHook) - { - ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always); - ImGui.SetNextWindowBgAlpha(1); - - if (ImGui.Begin( - "Disclaimer", - ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | - ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | - ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoMouseInputs | - ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "Is force MinHook!"); - } - - ImGui.End(); - } - - ImGui.PopStyleVar(4); - ImGui.PopStyleColor(7); + ImGui.End(); } + + ImGui.PopStyleVar(4); + ImGui.PopStyleColor(7); } + } - private void DrawDevMenu() + private void DrawDevMenu() + { + if (this.isImGuiDrawDevMenu) { - if (this.isImGuiDrawDevMenu) + if (ImGui.BeginMainMenuBar()) { - if (ImGui.BeginMainMenuBar()) + var dalamud = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + if (ImGui.BeginMenu("Dalamud")) { - var dalamud = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - if (ImGui.BeginMenu("Dalamud")) + ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu); + var devBarAtStartup = configuration.DevBarOpenAtStartup; + if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup)) { - ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu); - var devBarAtStartup = configuration.DevBarOpenAtStartup; - if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup)) - { - configuration.DevBarOpenAtStartup ^= true; - configuration.Save(); - } - - 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, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel)) - { - EntryPoint.LogLevelSwitch.MinimumLevel = logLevel; - configuration.LogLevel = logLevel; - configuration.Save(); - } - } - - ImGui.EndMenu(); - } - - var logSynchronously = configuration.LogSynchronously; - if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously)) - { - configuration.LogSynchronously = logSynchronously; - configuration.Save(); - - var startupInfo = Service.Get(); - EntryPoint.InitLogging( - startupInfo.WorkingDirectory!, - startupInfo.BootShowConsole, - configuration.LogSynchronously); - } - - 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(); - } - - if (ImGui.MenuItem("Open Profiler")) - { - this.OpenProfiler(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Unload Dalamud")) - { - Service.Get().Unload(); - } - - if (ImGui.MenuItem("Restart game")) - { - [DllImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments); - - RaiseException(0x12345678, 0, 0, IntPtr.Zero); - Process.GetCurrentProcess().Kill(); - } - - if (ImGui.MenuItem("Kill game")) - { - Process.GetCurrentProcess().Kill(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Access Violation")) - { - Marshal.ReadByte(IntPtr.Zero); - } - - if (ImGui.MenuItem("Crash game (nullptr)")) - { - unsafe - { - var framework = Framework.Instance(); - framework->UIModule = (UIModule*)0; - } - } - - if (ImGui.MenuItem("Crash game (non-nullptr)")) - { - unsafe - { - var framework = Framework.Instance(); - framework->UIModule = (UIModule*)0x12345678; - } - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Dalamud branch switcher")) - { - this.OpenBranchSwitcher(); - } - - var startInfo = Service.Get(); - ImGui.MenuItem(Util.AssemblyVersion, false); - ImGui.MenuItem(startInfo.GameVersion.ToString(), false); - ImGui.MenuItem($"D: {Util.GetGitHash()} CS: {Util.GetGitHashClientStructs()}", false); - - ImGui.EndMenu(); + configuration.DevBarOpenAtStartup ^= true; + configuration.Save(); } - if (ImGui.BeginMenu("GUI")) + ImGui.Separator(); + + if (ImGui.MenuItem("Open Log window")) { - ImGui.MenuItem("Use Monospace font for following windows", string.Empty, ref this.isImGuiTestWindowsInMonospace); - ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); - ImGui.MenuItem("Draw ImPlot demo", string.Empty, ref this.isImPlotDrawDemoWindow); - ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow); + this.OpenLogWindow(); + } - ImGui.Separator(); - - var val = ImGuiManagedAsserts.AssertsEnabled; - if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) + if (ImGui.BeginMenu("Set log level...")) + { + foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast()) { - 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 (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel)) { - 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"; - } + EntryPoint.LogLevelSwitch.MinimumLevel = logLevel; + configuration.LogLevel = logLevel; + configuration.Save(); } - - 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); - } - - if (ImGui.MenuItem("Show dev bar info", null, configuration.ShowDevBarInfo)) - { - configuration.ShowDevBarInfo = !configuration.ShowDevBarInfo; } ImGui.EndMenu(); } - if (ImGui.BeginMenu("Game")) + var logSynchronously = configuration.LogSynchronously; + if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously)) { - if (ImGui.MenuItem("Replace ExceptionHandler")) - { - dalamud.ReplaceExceptionHandler(); - } + configuration.LogSynchronously = logSynchronously; + configuration.Save(); - ImGui.EndMenu(); + var startupInfo = Service.Get(); + EntryPoint.InitLogging( + startupInfo.WorkingDirectory!, + startupInfo.BootShowConsole, + configuration.LogSynchronously); } - if (ImGui.BeginMenu("Plugins")) + var antiDebug = Service.Get(); + if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled)) { - if (ImGui.MenuItem("Open Plugin installer")) - { - this.OpenPluginInstaller(); - } + var newEnabled = !antiDebug.IsEnabled; + if (newEnabled) + antiDebug.Enable(); + else + antiDebug.Disable(); - 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("Scan dev plugins")) - { - pluginManager.ScanDevPlugins(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Load all API levels (ONLY FOR DEVELOPERS!!!)", null, pluginManager.LoadAllApiLevels)) - { - pluginManager.LoadAllApiLevels = !pluginManager.LoadAllApiLevels; - } - - if (ImGui.MenuItem("Load blacklisted plugins", null, pluginManager.LoadBannedPlugins)) - { - pluginManager.LoadBannedPlugins = !pluginManager.LoadBannedPlugins; - } - - ImGui.Separator(); - ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); - ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count, false); - ImGui.EndMenu(); + configuration.IsAntiAntiDebugEnabled = newEnabled; + configuration.Save(); } - if (ImGui.BeginMenu("Localization")) + ImGui.Separator(); + + if (ImGui.MenuItem("Open Data window")) { - 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(); + this.OpenDataWindow(); } - if (Service.Get().GameUiHidden) - ImGui.BeginMenu("UI is hidden...", false); - - if (configuration.ShowDevBarInfo) + if (ImGui.MenuItem("Open Credits window")) { - ImGui.PushFont(InterfaceManager.MonoFont); - - ImGui.BeginMenu(Util.GetGitHash(), false); - ImGui.BeginMenu(this.FrameCount.ToString("000000"), false); - ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false); - ImGui.BeginMenu($"{Util.FormatBytes(GC.GetTotalMemory(false))}", false); - - ImGui.PopFont(); + this.OpenCreditsWindow(); } - ImGui.EndMainMenuBar(); + if (ImGui.MenuItem("Open Settings window")) + { + this.OpenSettings(); + } + + if (ImGui.MenuItem("Open Changelog window")) + { + this.OpenChangelogWindow(); + } + + if (ImGui.MenuItem("Open Components Demo")) + { + this.OpenComponentDemoWindow(); + } + + if (ImGui.MenuItem("Open Colors Demo")) + { + this.OpenColorsDemoWindow(); + } + + if (ImGui.MenuItem("Open Self-Test")) + { + this.OpenSelfTest(); + } + + if (ImGui.MenuItem("Open Style Editor")) + { + this.OpenStyleEditor(); + } + + if (ImGui.MenuItem("Open Profiler")) + { + this.OpenProfiler(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Unload Dalamud")) + { + Service.Get().Unload(); + } + + if (ImGui.MenuItem("Restart game")) + { + [DllImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments); + + RaiseException(0x12345678, 0, 0, IntPtr.Zero); + Process.GetCurrentProcess().Kill(); + } + + if (ImGui.MenuItem("Kill game")) + { + Process.GetCurrentProcess().Kill(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Access Violation")) + { + Marshal.ReadByte(IntPtr.Zero); + } + + if (ImGui.MenuItem("Crash game (nullptr)")) + { + unsafe + { + var framework = Framework.Instance(); + framework->UIModule = (UIModule*)0; + } + } + + if (ImGui.MenuItem("Crash game (non-nullptr)")) + { + unsafe + { + var framework = Framework.Instance(); + framework->UIModule = (UIModule*)0x12345678; + } + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Dalamud branch switcher")) + { + this.OpenBranchSwitcher(); + } + + var startInfo = Service.Get(); + ImGui.MenuItem(Util.AssemblyVersion, false); + ImGui.MenuItem(startInfo.GameVersion.ToString(), false); + ImGui.MenuItem($"D: {Util.GetGitHash()} CS: {Util.GetGitHashClientStructs()}", false); + + ImGui.EndMenu(); } + + if (ImGui.BeginMenu("GUI")) + { + ImGui.MenuItem("Use Monospace font for following windows", string.Empty, ref this.isImGuiTestWindowsInMonospace); + ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); + ImGui.MenuItem("Draw ImPlot demo", string.Empty, ref this.isImPlotDrawDemoWindow); + ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow); + + ImGui.Separator(); + + var 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); + } + + if (ImGui.MenuItem("Show dev bar info", null, configuration.ShowDevBarInfo)) + { + configuration.ShowDevBarInfo = !configuration.ShowDevBarInfo; + } + + 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("Scan dev plugins")) + { + pluginManager.ScanDevPlugins(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Load all API levels (ONLY FOR DEVELOPERS!!!)", null, pluginManager.LoadAllApiLevels)) + { + pluginManager.LoadAllApiLevels = !pluginManager.LoadAllApiLevels; + } + + if (ImGui.MenuItem("Load blacklisted plugins", null, pluginManager.LoadBannedPlugins)) + { + pluginManager.LoadBannedPlugins = !pluginManager.LoadBannedPlugins; + } + + 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); + + if (configuration.ShowDevBarInfo) + { + ImGui.PushFont(InterfaceManager.MonoFont); + + ImGui.BeginMenu(Util.GetGitHash(), false); + ImGui.BeginMenu(this.FrameCount.ToString("000000"), false); + ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false); + ImGui.BeginMenu($"{Util.FormatBytes(GC.GetTotalMemory(false))}", false); + + ImGui.PopFont(); + } + + ImGui.EndMainMenuBar(); } } } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 62abeaf1d..98e0c72f1 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -40,1225 +40,1224 @@ 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. +/// +[ServiceManager.BlockingEarlyLoadedService] +internal class InterfaceManager : IDisposable, IServiceType { - /// - /// This class manages interaction with the ImGui interface. - /// - [ServiceManager.BlockingEarlyLoadedService] - internal class InterfaceManager : IDisposable, IServiceType + private const float DefaultFontSizePt = 12.0f; + private const float DefaultFontSizePx = DefaultFontSizePt * 4.0f / 3.0f; + private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing. + private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable. + + private readonly HashSet glyphRequests = new(); + private readonly Dictionary loadedFontInfo = new(); + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + + private readonly ManualResetEvent fontBuildSignal; + private readonly SwapChainVtableResolver address; + private readonly Hook dispatchMessageWHook; + private readonly Hook setCursorHook; + private Hook processMessageHook; + private RawDX11Scene? scene; + + private Hook? presentHook; + private Hook? resizeBuffersHook; + + // can't access imgui IO before first present call + private bool lastWantCapture = false; + private bool isRebuildingFonts = false; + private bool isOverrideGameCursor = true; + + [ServiceManager.ServiceConstructor] + private InterfaceManager() { - private const float DefaultFontSizePt = 12.0f; - private const float DefaultFontSizePx = DefaultFontSizePt * 4.0f / 3.0f; - private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing. - private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable. + this.dispatchMessageWHook = Hook.FromImport( + null, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour); + this.setCursorHook = Hook.FromImport( + null, "user32.dll", "SetCursor", 0, this.SetCursorDetour); - private readonly HashSet glyphRequests = new(); - private readonly Dictionary loadedFontInfo = new(); + this.fontBuildSignal = new ManualResetEvent(false); - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); + this.address = new SwapChainVtableResolver(); + } - private readonly ManualResetEvent fontBuildSignal; - private readonly SwapChainVtableResolver address; - private readonly Hook dispatchMessageWHook; - private readonly Hook setCursorHook; - private Hook processMessageHook; - private RawDX11Scene? scene; + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); - private Hook? presentHook; - private Hook? resizeBuffersHook; + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); - // can't access imgui IO before first present call - private bool lastWantCapture = false; - private bool isRebuildingFonts = false; - private bool isOverrideGameCursor = true; + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate IntPtr SetCursorDelegate(IntPtr hCursor); - [ServiceManager.ServiceConstructor] - private InterfaceManager() + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate IntPtr DispatchMessageWDelegate(ref User32.MSG msg); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr ProcessMessageDelegate(IntPtr hWnd, uint msg, ulong wParam, ulong lParam, IntPtr handeled); + + /// + /// 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 right before fonts are rebuilt. + /// + public event Action BuildFonts; + + /// + /// Gets or sets an action that is executed right after fonts are rebuilt. + /// + public event Action AfterBuildFonts; + + /// + /// 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 ?? IntPtr.Zero; + + /// + /// 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 ?? this.isOverrideGameCursor; + set { - this.dispatchMessageWHook = Hook.FromImport( - null, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour); - this.setCursorHook = Hook.FromImport( - null, "user32.dll", "SetCursor", 0, this.SetCursorDetour); - - this.fontBuildSignal = new ManualResetEvent(false); - - this.address = new SwapChainVtableResolver(); + this.isOverrideGameCursor = value; + if (this.scene != null) + this.scene.UpdateCursor = value; } + } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); + /// + /// Gets or sets a value indicating whether the fonts are built and ready to use. + /// + public bool FontsReady { get; set; } = false; - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); + /// + /// Gets a value indicating whether the Dalamud interface ready to use. + /// + public bool IsReady => this.scene != null; - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate IntPtr SetCursorDelegate(IntPtr hCursor); + /// + /// Gets or sets a value indicating whether or not Draw events should be dispatched. + /// + public bool IsDispatchingEvents { get; set; } = true; - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate IntPtr DispatchMessageWDelegate(ref User32.MSG msg); + /// + /// Gets or sets a value indicating whether to override configuration for UseAxis. + /// + public bool? UseAxisOverride { get; set; } = null; - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ProcessMessageDelegate(IntPtr hWnd, uint msg, ulong wParam, ulong lParam, IntPtr handeled); + /// + /// Gets a value indicating whether to use AXIS fonts. + /// + public bool UseAxis => this.UseAxisOverride ?? Service.Get().UseAxisFontsFromGame; - /// - /// This event gets called each frame to facilitate ImGui drawing. - /// - public event RawDX11Scene.BuildUIDelegate Draw; + /// + /// Gets or sets the overrided font gamma value, instead of using the value from configuration. + /// + public float? FontGammaOverride { get; set; } = null; - /// - /// This event gets called when ResizeBuffers is called. - /// - public event Action ResizeBuffers; + /// + /// Gets the font gamma value to use. + /// + public float FontGamma => Math.Max(0.1f, this.FontGammaOverride.GetValueOrDefault(Service.Get().FontGammaLevel)); - /// - /// Gets or sets an action that is executed right before fonts are rebuilt. - /// - public event Action BuildFonts; + /// + /// Gets a value indicating whether we're building fonts but haven't generated atlas yet. + /// + public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0); - /// - /// Gets or sets an action that is executed right after fonts are rebuilt. - /// - public event Action AfterBuildFonts; + /// + /// Gets a value indicating the native handle of the game main window. + /// + public IntPtr GameWindowHandle { get; private set; } - /// - /// 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 ?? IntPtr.Zero; - - /// - /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. - /// - public bool OverrideGameCursor + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.framework.RunOnFrameworkThread(() => { - get => this.scene?.UpdateCursor ?? this.isOverrideGameCursor; - set - { - this.isOverrideGameCursor = value; - if (this.scene != null) - this.scene.UpdateCursor = value; - } - } + this.setCursorHook.Dispose(); + this.presentHook?.Dispose(); + this.resizeBuffersHook?.Dispose(); + this.dispatchMessageWHook.Dispose(); + this.processMessageHook?.Dispose(); + }).Wait(); - /// - /// 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; - - /// - /// Gets or sets a value indicating whether or not Draw events should be dispatched. - /// - public bool IsDispatchingEvents { get; set; } = true; - - /// - /// Gets or sets a value indicating whether to override configuration for UseAxis. - /// - public bool? UseAxisOverride { get; set; } = null; - - /// - /// Gets a value indicating whether to use AXIS fonts. - /// - public bool UseAxis => this.UseAxisOverride ?? Service.Get().UseAxisFontsFromGame; - - /// - /// Gets or sets the overrided font gamma value, instead of using the value from configuration. - /// - public float? FontGammaOverride { get; set; } = null; - - /// - /// Gets the font gamma value to use. - /// - public float FontGamma => Math.Max(0.1f, this.FontGammaOverride.GetValueOrDefault(Service.Get().FontGammaLevel)); - - /// - /// Gets a value indicating whether we're building fonts but haven't generated atlas yet. - /// - public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0); - - /// - /// Gets a value indicating the native handle of the game main window. - /// - public IntPtr GameWindowHandle { get; private set; } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.framework.RunOnFrameworkThread(() => - { - this.setCursorHook.Dispose(); - this.presentHook?.Dispose(); - this.resizeBuffersHook?.Dispose(); - this.dispatchMessageWHook.Dispose(); - this.processMessageHook?.Dispose(); - }).Wait(); - - this.scene?.Dispose(); - } + this.scene?.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) + { + if (this.scene == null) + throw new InvalidOperationException("Scene isn't ready."); + + try { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - 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) + 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) + { + if (this.scene == null) + throw new InvalidOperationException("Scene isn't ready."); + + try { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - try - { - return this.scene?.LoadImage(imageData) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from memory"); - } - - return null; + 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) + 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) + { + if (this.scene == null) + throw new InvalidOperationException("Scene isn't ready."); + + try { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - 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 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() + { + if (this.scene == null) { - if (this.scene == null) + Log.Verbose("[FONT] RebuildFonts(): scene not ready, doing nothing"); + return; + } + + 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() trigger"); + this.isRebuildingFonts = true; + this.scene.OnNewRenderFrame += this.RebuildFontsInternal; + } + } + + /// + /// Wait for the rebuilding fonts to complete. + /// + public void WaitForFontRebuild() + { + this.fontBuildSignal.WaitOne(); + } + + /// + /// Requests a default font of specified size to exist. + /// + /// Font size in pixels. + /// Ranges of glyphs. + /// Requets handle. + public SpecialGlyphRequest NewFontSizeRef(float size, List> ranges) + { + var allContained = false; + var fonts = ImGui.GetIO().Fonts.Fonts; + ImFontPtr foundFont = null; + unsafe + { + for (int i = 0, i_ = fonts.Size; i < i_; i++) { - Log.Verbose("[FONT] RebuildFonts(): scene not ready, doing nothing"); + if (!this.glyphRequests.Any(x => x.FontInternal.NativePtr == fonts[i].NativePtr)) + continue; + + allContained = true; + foreach (var range in ranges) + { + if (!allContained) + break; + + for (var j = range.Item1; j <= range.Item2 && allContained; j++) + allContained &= fonts[i].FindGlyphNoFallback(j).NativePtr != null; + } + + if (allContained) + foundFont = fonts[i]; + + break; + } + } + + var req = new SpecialGlyphRequest(this, size, ranges); + req.FontInternal = foundFont; + + if (!allContained) + this.RebuildFonts(); + + return req; + } + + /// + /// Requests a default font of specified size to exist. + /// + /// Font size in pixels. + /// Text to calculate glyph ranges from. + /// Requets handle. + public SpecialGlyphRequest NewFontSizeRef(float size, string text) + { + List> ranges = new(); + foreach (var c in new SortedSet(text.ToHashSet())) + { + if (ranges.Any() && ranges[^1].Item2 + 1 == c) + ranges[^1] = Tuple.Create(ranges[^1].Item1, c); + else + ranges.Add(Tuple.Create(c, c)); + } + + return this.NewFontSizeRef(size, ranges); + } + + 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"); + } + + private void InitScene(IntPtr swapChain) + { + RawDX11Scene newScene; + using (Timings.Start("IM Scene Init")) + { + try + { + newScene = new RawDX11Scene(swapChain); + } + catch (DllNotFoundException ex) + { + Service.ProvideException(ex); + 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); + + if (res == User32.MessageBoxResult.IDYES) + { + var psi = new ProcessStartInfo + { + FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe", + UseShellExecute = true, + }; + Process.Start(psi); + } + + Environment.Exit(-1); + + // Doesn't reach here, but to make the compiler not complain return; } - 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() trigger"); - this.isRebuildingFonts = true; - this.scene.OnNewRenderFrame += this.RebuildFontsInternal; - } - } - - /// - /// Wait for the rebuilding fonts to complete. - /// - public void WaitForFontRebuild() - { - this.fontBuildSignal.WaitOne(); - } - - /// - /// Requests a default font of specified size to exist. - /// - /// Font size in pixels. - /// Ranges of glyphs. - /// Requets handle. - public SpecialGlyphRequest NewFontSizeRef(float size, List> ranges) - { - var allContained = false; - var fonts = ImGui.GetIO().Fonts.Fonts; - ImFontPtr foundFont = null; - unsafe - { - for (int i = 0, i_ = fonts.Size; i < i_; i++) - { - if (!this.glyphRequests.Any(x => x.FontInternal.NativePtr == fonts[i].NativePtr)) - continue; - - allContained = true; - foreach (var range in ranges) - { - if (!allContained) - break; - - for (var j = range.Item1; j <= range.Item2 && allContained; j++) - allContained &= fonts[i].FindGlyphNoFallback(j).NativePtr != null; - } - - if (allContained) - foundFont = fonts[i]; - - break; - } - } - - var req = new SpecialGlyphRequest(this, size, ranges); - req.FontInternal = foundFont; - - if (!allContained) - this.RebuildFonts(); - - return req; - } - - /// - /// Requests a default font of specified size to exist. - /// - /// Font size in pixels. - /// Text to calculate glyph ranges from. - /// Requets handle. - public SpecialGlyphRequest NewFontSizeRef(float size, string text) - { - List> ranges = new(); - foreach (var c in new SortedSet(text.ToHashSet())) - { - if (ranges.Any() && ranges[^1].Item2 + 1 == c) - ranges[^1] = Tuple.Create(ranges[^1].Item1, c); - else - ranges.Add(Tuple.Create(c, c)); - } - - return this.NewFontSizeRef(size, ranges); - } - - 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"); - } - - private void InitScene(IntPtr swapChain) - { - RawDX11Scene newScene; - using (Timings.Start("IM Scene Init")) - { - try - { - newScene = new RawDX11Scene(swapChain); - } - catch (DllNotFoundException ex) - { - Service.ProvideException(ex); - 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); - - if (res == User32.MessageBoxResult.IDYES) - { - var psi = new ProcessStartInfo - { - FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe", - UseShellExecute = true, - }; - Process.Start(psi); - } - - Environment.Exit(-1); - - // Doesn't reach here, but to make the compiler not complain - return; - } - - 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"); - } - - newScene.UpdateCursor = this.isOverrideGameCursor; - newScene.ImGuiIniPath = iniFileInfo.FullName; - newScene.OnBuildUI += this.Display; - newScene.OnNewInputFrame += this.OnNewInputFrame; - - 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; - - this.SetupFonts(); - - 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!"); - } - - this.scene = newScene; - Service.Provide(new(this)); - } - - /* - * 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) - this.InitScene(swapChain); - - if (this.address.IsReshade) - { - var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags); - - this.RenderImGui(); - - return pRes; - } - - 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 startInfo = Service.Get(); var configuration = Service.Get(); - if (configuration.IsDisableViewport || this.scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1) - { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; - return; - } - - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; - } - - /// - /// Loads font for use in ImGui text functions. - /// - private unsafe void SetupFonts() - { - using var setupFontsTimings = Timings.Start("IM SetupFonts"); - - var gameFontManager = Service.Get(); - var dalamud = Service.Get(); - var io = ImGui.GetIO(); - var ioFonts = io.Fonts; - - var fontGamma = this.FontGamma; - - this.fontBuildSignal.Reset(); - ioFonts.Clear(); - ioFonts.TexDesiredWidth = 4096; - - Log.Verbose("[FONT] SetupFonts - 1"); - - foreach (var v in this.loadedFontInfo) - v.Value.Dispose(); - - this.loadedFontInfo.Clear(); - - Log.Verbose("[FONT] SetupFonts - 2"); - - ImFontConfigPtr fontConfig = null; - List garbageList = new(); + var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini")); try { - var dummyRangeHandle = GCHandle.Alloc(new ushort[] { '0', '0', 0 }, GCHandleType.Pinned); - garbageList.Add(dummyRangeHandle); - - fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); - fontConfig.OversampleH = 1; - fontConfig.OversampleV = 1; - - var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Regular.otf"); - if (!File.Exists(fontPathJp)) - fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); - if (!File.Exists(fontPathJp)) - ShowFontError(fontPathJp); - Log.Verbose("[FONT] fontPathJp = {0}", fontPathJp); - - var fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKkr-Regular.otf"); - if (!File.Exists(fontPathKr)) - fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansKR-Regular.otf"); - if (!File.Exists(fontPathKr)) - fontPathKr = null; - Log.Verbose("[FONT] fontPathKr = {0}", fontPathKr); - - // Default font - Log.Verbose("[FONT] SetupFonts - Default font"); - var fontInfo = new TargetFontModification( - "Default", - this.UseAxis ? TargetFontModification.AxisMode.Overwrite : TargetFontModification.AxisMode.GameGlyphsOnly, - this.UseAxis ? DefaultFontSizePx : DefaultFontSizePx + 1, - io.FontGlobalScale); - Log.Verbose("[FONT] SetupFonts - Default corresponding AXIS size: {0}pt ({1}px)", fontInfo.SourceAxis.Style.BaseSizePt, fontInfo.SourceAxis.Style.BaseSizePx); - fontConfig.SizePixels = fontInfo.TargetSizePx * io.FontGlobalScale; - if (this.UseAxis) + if (iniFileInfo.Length > 1200000) { - fontConfig.GlyphRanges = dummyRangeHandle.AddrOfPinnedObject(); - fontConfig.PixelSnapH = false; - DefaultFont = ioFonts.AddFontDefault(fontConfig); - this.loadedFontInfo[DefaultFont] = fontInfo; + Log.Warning("dalamudUI.ini was over 1mb, deleting"); + iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); + iniFileInfo.Delete(); } - else - { - var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned); - garbageList.Add(japaneseRangeHandle); + } + catch (Exception ex) + { + Log.Error(ex, "Could not delete dalamudUI.ini"); + } - fontConfig.GlyphRanges = japaneseRangeHandle.AddrOfPinnedObject(); - fontConfig.PixelSnapH = true; - DefaultFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontConfig.SizePixels, fontConfig); - this.loadedFontInfo[DefaultFont] = fontInfo; + newScene.UpdateCursor = this.isOverrideGameCursor; + newScene.ImGuiIniPath = iniFileInfo.FullName; + newScene.OnBuildUI += this.Display; + newScene.OnNewInputFrame += this.OnNewInputFrame; + + 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; + + this.SetupFonts(); + + 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!"); + } + + this.scene = newScene; + Service.Provide(new(this)); + } + + /* + * 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) + this.InitScene(swapChain); + + if (this.address.IsReshade) + { + var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags); + + this.RenderImGui(); + + return pRes; + } + + 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) + { + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; + return; + } + + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; + } + + /// + /// Loads font for use in ImGui text functions. + /// + private unsafe void SetupFonts() + { + using var setupFontsTimings = Timings.Start("IM SetupFonts"); + + var gameFontManager = Service.Get(); + var dalamud = Service.Get(); + var io = ImGui.GetIO(); + var ioFonts = io.Fonts; + + var fontGamma = this.FontGamma; + + this.fontBuildSignal.Reset(); + ioFonts.Clear(); + ioFonts.TexDesiredWidth = 4096; + + Log.Verbose("[FONT] SetupFonts - 1"); + + foreach (var v in this.loadedFontInfo) + v.Value.Dispose(); + + this.loadedFontInfo.Clear(); + + Log.Verbose("[FONT] SetupFonts - 2"); + + ImFontConfigPtr fontConfig = null; + List garbageList = new(); + + try + { + var dummyRangeHandle = GCHandle.Alloc(new ushort[] { '0', '0', 0 }, GCHandleType.Pinned); + garbageList.Add(dummyRangeHandle); + + fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); + fontConfig.OversampleH = 1; + fontConfig.OversampleV = 1; + + var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Regular.otf"); + if (!File.Exists(fontPathJp)) + fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); + if (!File.Exists(fontPathJp)) + ShowFontError(fontPathJp); + Log.Verbose("[FONT] fontPathJp = {0}", fontPathJp); + + var fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKkr-Regular.otf"); + if (!File.Exists(fontPathKr)) + fontPathKr = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansKR-Regular.otf"); + if (!File.Exists(fontPathKr)) + fontPathKr = null; + Log.Verbose("[FONT] fontPathKr = {0}", fontPathKr); + + // Default font + Log.Verbose("[FONT] SetupFonts - Default font"); + var fontInfo = new TargetFontModification( + "Default", + this.UseAxis ? TargetFontModification.AxisMode.Overwrite : TargetFontModification.AxisMode.GameGlyphsOnly, + this.UseAxis ? DefaultFontSizePx : DefaultFontSizePx + 1, + io.FontGlobalScale); + Log.Verbose("[FONT] SetupFonts - Default corresponding AXIS size: {0}pt ({1}px)", fontInfo.SourceAxis.Style.BaseSizePt, fontInfo.SourceAxis.Style.BaseSizePx); + fontConfig.SizePixels = fontInfo.TargetSizePx * io.FontGlobalScale; + if (this.UseAxis) + { + fontConfig.GlyphRanges = dummyRangeHandle.AddrOfPinnedObject(); + fontConfig.PixelSnapH = false; + DefaultFont = ioFonts.AddFontDefault(fontConfig); + this.loadedFontInfo[DefaultFont] = fontInfo; + } + else + { + var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned); + garbageList.Add(japaneseRangeHandle); + + fontConfig.GlyphRanges = japaneseRangeHandle.AddrOfPinnedObject(); + fontConfig.PixelSnapH = true; + DefaultFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontConfig.SizePixels, fontConfig); + this.loadedFontInfo[DefaultFont] = fontInfo; + } + + if (fontPathKr != null && Service.Get().EffectiveLanguage == "ko") + { + fontConfig.MergeMode = true; + fontConfig.GlyphRanges = ioFonts.GetGlyphRangesKorean(); + fontConfig.PixelSnapH = true; + ioFonts.AddFontFromFileTTF(fontPathKr, fontConfig.SizePixels, fontConfig); + fontConfig.MergeMode = false; + } + + // FontAwesome icon font + Log.Verbose("[FONT] SetupFonts - FontAwesome icon font"); + { + var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesome5FreeSolid.otf"); + if (!File.Exists(fontPathIcon)) + ShowFontError(fontPathIcon); + + var iconRangeHandle = GCHandle.Alloc(new ushort[] { 0xE000, 0xF8FF, 0, }, GCHandleType.Pinned); + garbageList.Add(iconRangeHandle); + + fontConfig.GlyphRanges = iconRangeHandle.AddrOfPinnedObject(); + fontConfig.PixelSnapH = true; + IconFont = ioFonts.AddFontFromFileTTF(fontPathIcon, DefaultFontSizePx * io.FontGlobalScale, fontConfig); + this.loadedFontInfo[IconFont] = new("Icon", TargetFontModification.AxisMode.GameGlyphsOnly, DefaultFontSizePx, io.FontGlobalScale); + } + + // Monospace font + Log.Verbose("[FONT] SetupFonts - Monospace font"); + { + var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf"); + if (!File.Exists(fontPathMono)) + ShowFontError(fontPathMono); + + fontConfig.GlyphRanges = IntPtr.Zero; + fontConfig.PixelSnapH = true; + MonoFont = ioFonts.AddFontFromFileTTF(fontPathMono, DefaultFontSizePx * io.FontGlobalScale, fontConfig); + this.loadedFontInfo[MonoFont] = new("Mono", TargetFontModification.AxisMode.GameGlyphsOnly, DefaultFontSizePx, io.FontGlobalScale); + } + + // Default font but in requested size for requested glyphs + Log.Verbose("[FONT] SetupFonts - Default font but in requested size for requested glyphs"); + { + Dictionary> extraFontRequests = new(); + foreach (var extraFontRequest in this.glyphRequests) + { + if (!extraFontRequests.ContainsKey(extraFontRequest.Size)) + extraFontRequests[extraFontRequest.Size] = new(); + extraFontRequests[extraFontRequest.Size].Add(extraFontRequest); } - if (fontPathKr != null && Service.Get().EffectiveLanguage == "ko") + foreach (var (fontSize, requests) in extraFontRequests) { - fontConfig.MergeMode = true; - fontConfig.GlyphRanges = ioFonts.GetGlyphRangesKorean(); - fontConfig.PixelSnapH = true; - ioFonts.AddFontFromFileTTF(fontPathKr, fontConfig.SizePixels, fontConfig); - fontConfig.MergeMode = false; - } + List> codepointRanges = new(); + codepointRanges.Add(Tuple.Create(Fallback1Codepoint, Fallback1Codepoint)); + codepointRanges.Add(Tuple.Create(Fallback2Codepoint, Fallback2Codepoint)); - // FontAwesome icon font - Log.Verbose("[FONT] SetupFonts - FontAwesome icon font"); - { - var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesome5FreeSolid.otf"); - if (!File.Exists(fontPathIcon)) - ShowFontError(fontPathIcon); + // ImGui default ellipsis characters + codepointRanges.Add(Tuple.Create(0x2026, 0x2026)); + codepointRanges.Add(Tuple.Create(0x0085, 0x0085)); - var iconRangeHandle = GCHandle.Alloc(new ushort[] { 0xE000, 0xF8FF, 0, }, GCHandleType.Pinned); - garbageList.Add(iconRangeHandle); - - fontConfig.GlyphRanges = iconRangeHandle.AddrOfPinnedObject(); - fontConfig.PixelSnapH = true; - IconFont = ioFonts.AddFontFromFileTTF(fontPathIcon, DefaultFontSizePx * io.FontGlobalScale, fontConfig); - this.loadedFontInfo[IconFont] = new("Icon", TargetFontModification.AxisMode.GameGlyphsOnly, DefaultFontSizePx, io.FontGlobalScale); - } - - // Monospace font - Log.Verbose("[FONT] SetupFonts - Monospace font"); - { - var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf"); - if (!File.Exists(fontPathMono)) - ShowFontError(fontPathMono); - - fontConfig.GlyphRanges = IntPtr.Zero; - fontConfig.PixelSnapH = true; - MonoFont = ioFonts.AddFontFromFileTTF(fontPathMono, DefaultFontSizePx * io.FontGlobalScale, fontConfig); - this.loadedFontInfo[MonoFont] = new("Mono", TargetFontModification.AxisMode.GameGlyphsOnly, DefaultFontSizePx, io.FontGlobalScale); - } - - // Default font but in requested size for requested glyphs - Log.Verbose("[FONT] SetupFonts - Default font but in requested size for requested glyphs"); - { - Dictionary> extraFontRequests = new(); - foreach (var extraFontRequest in this.glyphRequests) + foreach (var request in requests) { - if (!extraFontRequests.ContainsKey(extraFontRequest.Size)) - extraFontRequests[extraFontRequest.Size] = new(); - extraFontRequests[extraFontRequest.Size].Add(extraFontRequest); + foreach (var range in request.CodepointRanges) + codepointRanges.Add(range); } - foreach (var (fontSize, requests) in extraFontRequests) + codepointRanges.Sort((x, y) => (x.Item1 == y.Item1 ? (x.Item2 < y.Item2 ? -1 : (x.Item2 == y.Item2 ? 0 : 1)) : (x.Item1 < y.Item1 ? -1 : 1))); + + List flattenedRanges = new(); + foreach (var range in codepointRanges) { - List> codepointRanges = new(); - codepointRanges.Add(Tuple.Create(Fallback1Codepoint, Fallback1Codepoint)); - codepointRanges.Add(Tuple.Create(Fallback2Codepoint, Fallback2Codepoint)); - - // ImGui default ellipsis characters - codepointRanges.Add(Tuple.Create(0x2026, 0x2026)); - codepointRanges.Add(Tuple.Create(0x0085, 0x0085)); - - foreach (var request in requests) + if (flattenedRanges.Any() && flattenedRanges[^1] >= range.Item1 - 1) { - foreach (var range in request.CodepointRanges) - codepointRanges.Add(range); - } - - codepointRanges.Sort((x, y) => (x.Item1 == y.Item1 ? (x.Item2 < y.Item2 ? -1 : (x.Item2 == y.Item2 ? 0 : 1)) : (x.Item1 < y.Item1 ? -1 : 1))); - - List flattenedRanges = new(); - foreach (var range in codepointRanges) - { - if (flattenedRanges.Any() && flattenedRanges[^1] >= range.Item1 - 1) - { - flattenedRanges[^1] = Math.Max(flattenedRanges[^1], range.Item2); - } - else - { - flattenedRanges.Add(range.Item1); - flattenedRanges.Add(range.Item2); - } - } - - flattenedRanges.Add(0); - - fontInfo = new( - $"Requested({fontSize}px)", - this.UseAxis ? TargetFontModification.AxisMode.Overwrite : TargetFontModification.AxisMode.GameGlyphsOnly, - fontSize, - io.FontGlobalScale); - if (this.UseAxis) - { - fontConfig.GlyphRanges = dummyRangeHandle.AddrOfPinnedObject(); - fontConfig.SizePixels = fontInfo.SourceAxis.Style.BaseSizePx; - fontConfig.PixelSnapH = false; - - var sizedFont = ioFonts.AddFontDefault(fontConfig); - this.loadedFontInfo[sizedFont] = fontInfo; - foreach (var request in requests) - request.FontInternal = sizedFont; + flattenedRanges[^1] = Math.Max(flattenedRanges[^1], range.Item2); } else { - var rangeHandle = GCHandle.Alloc(flattenedRanges.ToArray(), GCHandleType.Pinned); - garbageList.Add(rangeHandle); - fontConfig.PixelSnapH = true; - - var sizedFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontSize * io.FontGlobalScale, fontConfig, rangeHandle.AddrOfPinnedObject()); - this.loadedFontInfo[sizedFont] = fontInfo; - foreach (var request in requests) - request.FontInternal = sizedFont; + flattenedRanges.Add(range.Item1); + flattenedRanges.Add(range.Item2); } } - } - gameFontManager.BuildFonts(); + flattenedRanges.Add(0); - var customFontFirstConfigIndex = ioFonts.ConfigData.Size; - - Log.Verbose("[FONT] Invoke OnBuildFonts"); - this.BuildFonts?.InvokeSafely(); - Log.Verbose("[FONT] OnBuildFonts OK!"); - - for (int i = customFontFirstConfigIndex, i_ = ioFonts.ConfigData.Size; i < i_; i++) - { - var config = ioFonts.ConfigData[i]; - if (gameFontManager.OwnsFont(config.DstFont)) - continue; - - config.OversampleH = 1; - config.OversampleV = 1; - - var name = Encoding.UTF8.GetString((byte*)config.Name.Data, config.Name.Count).TrimEnd('\0'); - if (name.IsNullOrEmpty()) - name = $"{config.SizePixels}px"; - - // ImFont information is reflected only if corresponding ImFontConfig has MergeMode not set. - if (config.MergeMode) + fontInfo = new( + $"Requested({fontSize}px)", + this.UseAxis ? TargetFontModification.AxisMode.Overwrite : TargetFontModification.AxisMode.GameGlyphsOnly, + fontSize, + io.FontGlobalScale); + if (this.UseAxis) { - if (!this.loadedFontInfo.ContainsKey(config.DstFont.NativePtr)) - { - Log.Warning("MergeMode specified for {0} but not found in loadedFontInfo. Skipping.", name); - continue; - } + fontConfig.GlyphRanges = dummyRangeHandle.AddrOfPinnedObject(); + fontConfig.SizePixels = fontInfo.SourceAxis.Style.BaseSizePx; + fontConfig.PixelSnapH = false; + + var sizedFont = ioFonts.AddFontDefault(fontConfig); + this.loadedFontInfo[sizedFont] = fontInfo; + foreach (var request in requests) + request.FontInternal = sizedFont; } else { - if (this.loadedFontInfo.ContainsKey(config.DstFont.NativePtr)) - { - Log.Warning("MergeMode not specified for {0} but found in loadedFontInfo. Skipping.", name); - continue; - } + var rangeHandle = GCHandle.Alloc(flattenedRanges.ToArray(), GCHandleType.Pinned); + garbageList.Add(rangeHandle); + fontConfig.PixelSnapH = true; - // While the font will be loaded in the scaled size after FontScale is applied, the font will be treated as having the requested size when used from plugins. - this.loadedFontInfo[config.DstFont.NativePtr] = new($"PlReq({name})", config.SizePixels); + var sizedFont = ioFonts.AddFontFromFileTTF(fontPathJp, fontSize * io.FontGlobalScale, fontConfig, rangeHandle.AddrOfPinnedObject()); + this.loadedFontInfo[sizedFont] = fontInfo; + foreach (var request in requests) + request.FontInternal = sizedFont; } - - config.SizePixels = config.SizePixels * io.FontGlobalScale; } + } - for (int i = 0, i_ = ioFonts.ConfigData.Size; i < i_; i++) + gameFontManager.BuildFonts(); + + var customFontFirstConfigIndex = ioFonts.ConfigData.Size; + + Log.Verbose("[FONT] Invoke OnBuildFonts"); + this.BuildFonts?.InvokeSafely(); + Log.Verbose("[FONT] OnBuildFonts OK!"); + + for (int i = customFontFirstConfigIndex, i_ = ioFonts.ConfigData.Size; i < i_; i++) + { + var config = ioFonts.ConfigData[i]; + if (gameFontManager.OwnsFont(config.DstFont)) + continue; + + config.OversampleH = 1; + config.OversampleV = 1; + + var name = Encoding.UTF8.GetString((byte*)config.Name.Data, config.Name.Count).TrimEnd('\0'); + if (name.IsNullOrEmpty()) + name = $"{config.SizePixels}px"; + + // ImFont information is reflected only if corresponding ImFontConfig has MergeMode not set. + if (config.MergeMode) { - var config = ioFonts.ConfigData[i]; - config.RasterizerGamma *= fontGamma; + if (!this.loadedFontInfo.ContainsKey(config.DstFont.NativePtr)) + { + Log.Warning("MergeMode specified for {0} but not found in loadedFontInfo. Skipping.", name); + continue; + } } - - Log.Verbose("[FONT] ImGui.IO.Build will be called."); - ioFonts.Build(); - gameFontManager.AfterIoFontsBuild(); - Log.Verbose("[FONT] ImGui.IO.Build OK!"); - - gameFontManager.AfterBuildFonts(); - - foreach (var (font, mod) in this.loadedFontInfo) + else { - // I have no idea what's causing NPE, so just to be safe - try + if (this.loadedFontInfo.ContainsKey(config.DstFont.NativePtr)) { - if (font.NativePtr != null && font.NativePtr->ConfigData != null) - { - var nameBytes = Encoding.UTF8.GetBytes($"{mod.Name}\0"); - Marshal.Copy(nameBytes, 0, (IntPtr)font.ConfigData.Name.Data, Math.Min(nameBytes.Length, font.ConfigData.Name.Count)); - } - } - catch (NullReferenceException) - { - // do nothing - } - - Log.Verbose("[FONT] {0}: Unscale with scale value of {1}", mod.Name, mod.Scale); - GameFontManager.UnscaleFont(font, mod.Scale, false); - - if (mod.Axis == TargetFontModification.AxisMode.Overwrite) - { - Log.Verbose("[FONT] {0}: Overwrite from AXIS of size {1}px (was {2}px)", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize); - GameFontManager.UnscaleFont(font, font.FontSize / mod.SourceAxis.ImFont.FontSize, false); - var ascentDiff = mod.SourceAxis.ImFont.Ascent - font.Ascent; - font.Ascent += ascentDiff; - font.Descent = ascentDiff; - font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar; - font.EllipsisChar = mod.SourceAxis.ImFont.EllipsisChar; - ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, false, false); - } - else if (mod.Axis == TargetFontModification.AxisMode.GameGlyphsOnly) - { - Log.Verbose("[FONT] {0}: Overwrite game specific glyphs from AXIS of size {1}px", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize); - if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr) - mod.SourceAxis.ImFont.FontSize -= 1; - ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, true, false, 0xE020, 0xE0DB); - if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr) - mod.SourceAxis.ImFont.FontSize += 1; - } - - Log.Verbose("[FONT] {0}: Resize from {1}px to {2}px", mod.Name, font.FontSize, mod.TargetSizePx); - GameFontManager.UnscaleFont(font, font.FontSize / mod.TargetSizePx, false); - } - - // Fill missing glyphs in MonoFont from DefaultFont - ImGuiHelpers.CopyGlyphsAcrossFonts(DefaultFont, MonoFont, true, false); - - for (int i = 0, i_ = ioFonts.Fonts.Size; i < i_; i++) - { - var font = ioFonts.Fonts[i]; - if (font.Glyphs.Size == 0) - { - Log.Warning("[FONT] Font has no glyph: {0}", font.GetDebugName()); + Log.Warning("MergeMode not specified for {0} but found in loadedFontInfo. Skipping.", name); continue; } - if (font.FindGlyphNoFallback(Fallback1Codepoint).NativePtr != null) - font.FallbackChar = Fallback1Codepoint; - - font.BuildLookupTable(); + // While the font will be loaded in the scaled size after FontScale is applied, the font will be treated as having the requested size when used from plugins. + this.loadedFontInfo[config.DstFont.NativePtr] = new($"PlReq({name})", config.SizePixels); } - Log.Verbose("[FONT] Invoke OnAfterBuildFonts"); - this.AfterBuildFonts?.InvokeSafely(); - Log.Verbose("[FONT] OnAfterBuildFonts OK!"); - - if (ioFonts.Fonts[0].NativePtr != DefaultFont.NativePtr) - Log.Warning("[FONT] First font is not DefaultFont"); - - Log.Verbose("[FONT] Fonts built!"); - - this.fontBuildSignal.Set(); - - this.FontsReady = true; + config.SizePixels = config.SizePixels * io.FontGlobalScale; } - finally - { - if (fontConfig.NativePtr != null) - fontConfig.Destroy(); - foreach (var garbage in garbageList) - garbage.Free(); + for (int i = 0, i_ = ioFonts.ConfigData.Size; i < i_; i++) + { + var config = ioFonts.ConfigData[i]; + config.RasterizerGamma *= fontGamma; } - } - [ServiceManager.CallWhenServicesReady] - private void ContinueConstruction(SigScanner sigScanner, Framework framework) - { - this.address.Setup(sigScanner); - framework.RunOnFrameworkThread(() => + Log.Verbose("[FONT] ImGui.IO.Build will be called."); + ioFonts.Build(); + gameFontManager.AfterIoFontsBuild(); + Log.Verbose("[FONT] ImGui.IO.Build OK!"); + + gameFontManager.AfterBuildFonts(); + + foreach (var (font, mod) in this.loadedFontInfo) { - while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero) + // I have no idea what's causing NPE, so just to be safe + try { - _ = User32.GetWindowThreadProcessId(this.GameWindowHandle, out var pid); - - if (pid == Environment.ProcessId && User32.IsWindowVisible(this.GameWindowHandle)) - break; + if (font.NativePtr != null && font.NativePtr->ConfigData != null) + { + var nameBytes = Encoding.UTF8.GetBytes($"{mod.Name}\0"); + Marshal.Copy(nameBytes, 0, (IntPtr)font.ConfigData.Name.Data, Math.Min(nameBytes.Length, font.ConfigData.Name.Count)); + } + } + catch (NullReferenceException) + { + // do nothing } - this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour); - this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour); + Log.Verbose("[FONT] {0}: Unscale with scale value of {1}", mod.Name, mod.Scale); + GameFontManager.UnscaleFont(font, mod.Scale, false); - Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}"); - Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}"); + if (mod.Axis == TargetFontModification.AxisMode.Overwrite) + { + Log.Verbose("[FONT] {0}: Overwrite from AXIS of size {1}px (was {2}px)", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize); + GameFontManager.UnscaleFont(font, font.FontSize / mod.SourceAxis.ImFont.FontSize, false); + var ascentDiff = mod.SourceAxis.ImFont.Ascent - font.Ascent; + font.Ascent += ascentDiff; + font.Descent = ascentDiff; + font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar; + font.EllipsisChar = mod.SourceAxis.ImFont.EllipsisChar; + ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, false, false); + } + else if (mod.Axis == TargetFontModification.AxisMode.GameGlyphsOnly) + { + Log.Verbose("[FONT] {0}: Overwrite game specific glyphs from AXIS of size {1}px", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize); + if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr) + mod.SourceAxis.ImFont.FontSize -= 1; + ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, true, false, 0xE020, 0xE0DB); + if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr) + mod.SourceAxis.ImFont.FontSize += 1; + } - var wndProcAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 80 7C 24 ?? ?? 74 ?? B8"); - Log.Verbose($"WndProc address 0x{wndProcAddress.ToInt64():X}"); - this.processMessageHook = Hook.FromAddress(wndProcAddress, this.ProcessMessageDetour); - - this.setCursorHook.Enable(); - this.presentHook.Enable(); - this.resizeBuffersHook.Enable(); - this.dispatchMessageWHook.Enable(); - this.processMessageHook.Enable(); - }); - } - - // 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; - - Log.Verbose("[FONT] Calling InvalidateFonts"); - this.scene.InvalidateFonts(); - - Log.Verbose("[FONT] Font Rebuild OK!"); - - this.isRebuildingFonts = false; - } - - private unsafe IntPtr ProcessMessageDetour(IntPtr hWnd, uint msg, ulong wParam, ulong lParam, IntPtr handeled) - { - var ime = Service.GetNullable(); - var res = ime?.ProcessWndProcW(hWnd, (User32.WindowMessage)msg, (void*)wParam, (void*)lParam); - return this.processMessageHook.Original(hWnd, msg, wParam, lParam, handeled); - } - - private unsafe IntPtr DispatchMessageWDetour(ref User32.MSG msg) - { - if (msg.hwnd == this.GameWindowHandle && this.scene != null) - { - var res = this.scene.ProcessWndProcW(msg.hwnd, msg.message, (void*)msg.wParam, (void*)msg.lParam); - if (res != null) - return res.Value; + Log.Verbose("[FONT] {0}: Resize from {1}px to {2}px", mod.Name, font.FontSize, mod.TargetSizePx); + GameFontManager.UnscaleFont(font, font.FontSize / mod.TargetSizePx, false); } - return this.dispatchMessageWHook.IsDisposed ? User32.DispatchMessage(ref msg) : this.dispatchMessageWHook.Original(ref msg); + // Fill missing glyphs in MonoFont from DefaultFont + ImGuiHelpers.CopyGlyphsAcrossFonts(DefaultFont, MonoFont, true, false); + + for (int i = 0, i_ = ioFonts.Fonts.Size; i < i_; i++) + { + var font = ioFonts.Fonts[i]; + if (font.Glyphs.Size == 0) + { + Log.Warning("[FONT] Font has no glyph: {0}", font.GetDebugName()); + continue; + } + + if (font.FindGlyphNoFallback(Fallback1Codepoint).NativePtr != null) + font.FallbackChar = Fallback1Codepoint; + + font.BuildLookupTable(); + } + + Log.Verbose("[FONT] Invoke OnAfterBuildFonts"); + this.AfterBuildFonts?.InvokeSafely(); + Log.Verbose("[FONT] OnAfterBuildFonts OK!"); + + if (ioFonts.Fonts[0].NativePtr != DefaultFont.NativePtr) + Log.Warning("[FONT] First font is not DefaultFont"); + + Log.Verbose("[FONT] Fonts built!"); + + this.fontBuildSignal.Set(); + + this.FontsReady = true; + } + finally + { + if (fontConfig.NativePtr != null) + fontConfig.Destroy(); + + foreach (var garbage in garbageList) + garbage.Free(); + } + } + + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(SigScanner sigScanner, Framework framework) + { + this.address.Setup(sigScanner); + framework.RunOnFrameworkThread(() => + { + while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero) + { + _ = User32.GetWindowThreadProcessId(this.GameWindowHandle, out var pid); + + if (pid == Environment.ProcessId && User32.IsWindowVisible(this.GameWindowHandle)) + break; + } + + this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour); + this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour); + + Log.Verbose("===== S W A P C H A I N ====="); + Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}"); + Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}"); + + var wndProcAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 80 7C 24 ?? ?? 74 ?? B8"); + Log.Verbose($"WndProc address 0x{wndProcAddress.ToInt64():X}"); + this.processMessageHook = Hook.FromAddress(wndProcAddress, this.ProcessMessageDetour); + + this.setCursorHook.Enable(); + this.presentHook.Enable(); + this.resizeBuffersHook.Enable(); + this.dispatchMessageWHook.Enable(); + this.processMessageHook.Enable(); + }); + } + + // 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; + + Log.Verbose("[FONT] Calling InvalidateFonts"); + this.scene.InvalidateFonts(); + + Log.Verbose("[FONT] Font Rebuild OK!"); + + this.isRebuildingFonts = false; + } + + private unsafe IntPtr ProcessMessageDetour(IntPtr hWnd, uint msg, ulong wParam, ulong lParam, IntPtr handeled) + { + var ime = Service.GetNullable(); + var res = ime?.ProcessWndProcW(hWnd, (User32.WindowMessage)msg, (void*)wParam, (void*)lParam); + return this.processMessageHook.Original(hWnd, msg, wParam, lParam, handeled); + } + + private unsafe IntPtr DispatchMessageWDetour(ref User32.MSG msg) + { + if (msg.hwnd == this.GameWindowHandle && this.scene != null) + { + var res = this.scene.ProcessWndProcW(msg.hwnd, msg.message, (void*)msg.wParam, (void*)msg.lParam); + if (res != null) + return res.Value; } - private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) - { + return this.dispatchMessageWHook.IsDisposed ? User32.DispatchMessage(ref msg) : this.dispatchMessageWHook.Original(ref msg); + } + + 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}"); #endif - this.ResizeBuffers?.InvokeSafely(); + this.ResizeBuffers?.InvokeSafely(); - // 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.IsDisposed ? User32.SetCursor(new User32.SafeCursorHandle(hCursor, false)).DangerousGetHandle() : 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.IsDisposed ? User32.SetCursor(new User32.SafeCursorHandle(hCursor, false)).DangerousGetHandle() : this.setCursorHook.Original(hCursor); + } + + private void OnNewInputFrame() + { + var dalamudInterface = Service.GetNullable(); + var gamepadState = Service.GetNullable(); + var keyState = Service.GetNullable(); + + if (dalamudInterface == null || gamepadState == null || keyState == null) + return; + + // 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(); - - if (dalamudInterface == null || gamepadState == null || keyState == null) - return; - - // 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) - { - var northButton = gamepadState.Raw(GamepadButtons.North) != 0; - var eastButton = gamepadState.Raw(GamepadButtons.East) != 0; - var southButton = gamepadState.Raw(GamepadButtons.South) != 0; - var westButton = gamepadState.Raw(GamepadButtons.West) != 0; - var dPadUp = gamepadState.Raw(GamepadButtons.DpadUp) != 0; - var dPadRight = gamepadState.Raw(GamepadButtons.DpadRight) != 0; - var dPadDown = gamepadState.Raw(GamepadButtons.DpadDown) != 0; - var dPadLeft = gamepadState.Raw(GamepadButtons.DpadLeft) != 0; - var leftStickUp = gamepadState.LeftStickUp; - var leftStickRight = gamepadState.LeftStickRight; - var leftStickDown = gamepadState.LeftStickDown; - var leftStickLeft = gamepadState.LeftStickLeft; - var l1Button = gamepadState.Raw(GamepadButtons.L1) != 0; - var l2Button = gamepadState.Raw(GamepadButtons.L2) != 0; - var r1Button = gamepadState.Raw(GamepadButtons.R1) != 0; - var r2Button = gamepadState.Raw(GamepadButtons.R2) != 0; - - var io = ImGui.GetIO(); - io.AddKeyEvent(ImGuiKey.GamepadFaceUp, northButton); - io.AddKeyEvent(ImGuiKey.GamepadFaceRight, eastButton); - io.AddKeyEvent(ImGuiKey.GamepadFaceDown, southButton); - io.AddKeyEvent(ImGuiKey.GamepadFaceLeft, westButton); - - io.AddKeyEvent(ImGuiKey.GamepadDpadUp, dPadUp); - io.AddKeyEvent(ImGuiKey.GamepadDpadRight, dPadRight); - io.AddKeyEvent(ImGuiKey.GamepadDpadDown, dPadDown); - io.AddKeyEvent(ImGuiKey.GamepadDpadLeft, dPadLeft); - - io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickUp, leftStickUp != 0, leftStickUp); - io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickRight, leftStickRight != 0, leftStickRight); - io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickDown, leftStickDown != 0, leftStickDown); - io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickLeft, leftStickLeft != 0, leftStickLeft); - - io.AddKeyEvent(ImGuiKey.GamepadL1, l1Button); - io.AddKeyEvent(ImGuiKey.GamepadL2, l2Button); - io.AddKeyEvent(ImGuiKey.GamepadR1, r1Button); - io.AddKeyEvent(ImGuiKey.GamepadR2, r2Button); - - 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; + var northButton = gamepadState.Raw(GamepadButtons.North) != 0; + var eastButton = gamepadState.Raw(GamepadButtons.East) != 0; + var southButton = gamepadState.Raw(GamepadButtons.South) != 0; + var westButton = gamepadState.Raw(GamepadButtons.West) != 0; + var dPadUp = gamepadState.Raw(GamepadButtons.DpadUp) != 0; + var dPadRight = gamepadState.Raw(GamepadButtons.DpadRight) != 0; + var dPadDown = gamepadState.Raw(GamepadButtons.DpadDown) != 0; + var dPadLeft = gamepadState.Raw(GamepadButtons.DpadLeft) != 0; + var leftStickUp = gamepadState.LeftStickUp; + var leftStickRight = gamepadState.LeftStickRight; + var leftStickDown = gamepadState.LeftStickDown; + var leftStickLeft = gamepadState.LeftStickLeft; + var l1Button = gamepadState.Raw(GamepadButtons.L1) != 0; + var l2Button = gamepadState.Raw(GamepadButtons.L2) != 0; + var r1Button = gamepadState.Raw(GamepadButtons.R1) != 0; + var r2Button = gamepadState.Raw(GamepadButtons.R2) != 0; - WindowSystem.HasAnyWindowSystemFocus = false; - WindowSystem.FocusedWindowSystemNamespace = string.Empty; + var io = ImGui.GetIO(); + io.AddKeyEvent(ImGuiKey.GamepadFaceUp, northButton); + io.AddKeyEvent(ImGuiKey.GamepadFaceRight, eastButton); + io.AddKeyEvent(ImGuiKey.GamepadFaceDown, southButton); + io.AddKeyEvent(ImGuiKey.GamepadFaceLeft, westButton); - var snap = ImGuiManagedAsserts.GetSnapshot(); + io.AddKeyEvent(ImGuiKey.GamepadDpadUp, dPadUp); + io.AddKeyEvent(ImGuiKey.GamepadDpadRight, dPadRight); + io.AddKeyEvent(ImGuiKey.GamepadDpadDown, dPadDown); + io.AddKeyEvent(ImGuiKey.GamepadDpadLeft, dPadLeft); - if (this.IsDispatchingEvents) - this.Draw?.Invoke(); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickUp, leftStickUp != 0, leftStickUp); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickRight, leftStickRight != 0, leftStickRight); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickDown, leftStickDown != 0, leftStickDown); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickLeft, leftStickLeft != 0, leftStickLeft); - ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); + io.AddKeyEvent(ImGuiKey.GamepadL1, l1Button); + io.AddKeyEvent(ImGuiKey.GamepadL2, l2Button); + io.AddKeyEvent(ImGuiKey.GamepadR1, r1Button); + io.AddKeyEvent(ImGuiKey.GamepadR2, r2Button); - Service.Get().Draw(); - } - - /// - /// Represents an instance of InstanceManager with scene ready for use. - /// - public class InterfaceManagerWithScene : IServiceType - { - /// - /// Initializes a new instance of the class. - /// - /// An instance of . - internal InterfaceManagerWithScene(InterfaceManager interfaceManager) + if (gamepadState.Pressed(GamepadButtons.R3) > 0) { - this.Manager = interfaceManager; - } - - /// - /// Gets the associated InterfaceManager. - /// - public InterfaceManager Manager { get; init; } - } - - /// - /// Represents a glyph request. - /// - public class SpecialGlyphRequest : IDisposable - { - /// - /// Initializes a new instance of the class. - /// - /// InterfaceManager to associate. - /// Font size in pixels. - /// Codepoint ranges. - internal SpecialGlyphRequest(InterfaceManager manager, float size, List> ranges) - { - this.Manager = manager; - this.Size = size; - this.CodepointRanges = ranges; - this.Manager.glyphRequests.Add(this); - } - - /// - /// Gets the font of specified size, or DefaultFont if it's not ready yet. - /// - public ImFontPtr Font - { - get - { - unsafe - { - return this.FontInternal.NativePtr == null ? DefaultFont : this.FontInternal; - } - } - } - - /// - /// Gets or sets the associated ImFont. - /// - internal ImFontPtr FontInternal { get; set; } - - /// - /// Gets associated InterfaceManager. - /// - internal InterfaceManager Manager { get; init; } - - /// - /// Gets font size. - /// - internal float Size { get; init; } - - /// - /// Gets codepoint ranges. - /// - internal List> CodepointRanges { get; init; } - - /// - public void Dispose() - { - this.Manager.glyphRequests.Remove(this); - } - } - - private unsafe class TargetFontModification : IDisposable - { - /// - /// Initializes a new instance of the class. - /// Constructs new target font modification information, assuming that AXIS fonts will not be applied. - /// - /// Name of the font to write to ImGui font information. - /// Target font size in pixels, which will not be considered for further scaling. - internal TargetFontModification(string name, float sizePx) - { - this.Name = name; - this.Axis = AxisMode.Suppress; - this.TargetSizePx = sizePx; - this.Scale = 1; - this.SourceAxis = null; - } - - /// - /// Initializes a new instance of the class. - /// Constructs new target font modification information. - /// - /// Name of the font to write to ImGui font information. - /// Whether and how to use AXIS fonts. - /// Target font size in pixels, which will not be considered for further scaling. - /// Font scale to be referred for loading AXIS font of appropriate size. - internal TargetFontModification(string name, AxisMode axis, float sizePx, float globalFontScale) - { - this.Name = name; - this.Axis = axis; - this.TargetSizePx = sizePx; - this.Scale = globalFontScale; - this.SourceAxis = Service.Get().NewFontRef(new(GameFontFamily.Axis, this.TargetSizePx * this.Scale)); - } - - internal enum AxisMode - { - Suppress, - GameGlyphsOnly, - Overwrite, - } - - internal string Name { get; private init; } - - internal AxisMode Axis { get; private init; } - - internal float TargetSizePx { get; private init; } - - internal float Scale { get; private init; } - - internal GameFontHandle? SourceAxis { get; private init; } - - internal bool SourceAxisAvailable => this.SourceAxis != null && this.SourceAxis.ImFont.NativePtr != null; - - public void Dispose() - { - this.SourceAxis?.Dispose(); + 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(); + + if (this.IsDispatchingEvents) + this.Draw?.Invoke(); + + ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); + + Service.Get().Draw(); + } + + /// + /// Represents an instance of InstanceManager with scene ready for use. + /// + public class InterfaceManagerWithScene : IServiceType + { + /// + /// Initializes a new instance of the class. + /// + /// An instance of . + internal InterfaceManagerWithScene(InterfaceManager interfaceManager) + { + this.Manager = interfaceManager; + } + + /// + /// Gets the associated InterfaceManager. + /// + public InterfaceManager Manager { get; init; } + } + + /// + /// Represents a glyph request. + /// + public class SpecialGlyphRequest : IDisposable + { + /// + /// Initializes a new instance of the class. + /// + /// InterfaceManager to associate. + /// Font size in pixels. + /// Codepoint ranges. + internal SpecialGlyphRequest(InterfaceManager manager, float size, List> ranges) + { + this.Manager = manager; + this.Size = size; + this.CodepointRanges = ranges; + this.Manager.glyphRequests.Add(this); + } + + /// + /// Gets the font of specified size, or DefaultFont if it's not ready yet. + /// + public ImFontPtr Font + { + get + { + unsafe + { + return this.FontInternal.NativePtr == null ? DefaultFont : this.FontInternal; + } + } + } + + /// + /// Gets or sets the associated ImFont. + /// + internal ImFontPtr FontInternal { get; set; } + + /// + /// Gets associated InterfaceManager. + /// + internal InterfaceManager Manager { get; init; } + + /// + /// Gets font size. + /// + internal float Size { get; init; } + + /// + /// Gets codepoint ranges. + /// + internal List> CodepointRanges { get; init; } + + /// + public void Dispose() + { + this.Manager.glyphRequests.Remove(this); + } + } + + private unsafe class TargetFontModification : IDisposable + { + /// + /// Initializes a new instance of the class. + /// Constructs new target font modification information, assuming that AXIS fonts will not be applied. + /// + /// Name of the font to write to ImGui font information. + /// Target font size in pixels, which will not be considered for further scaling. + internal TargetFontModification(string name, float sizePx) + { + this.Name = name; + this.Axis = AxisMode.Suppress; + this.TargetSizePx = sizePx; + this.Scale = 1; + this.SourceAxis = null; + } + + /// + /// Initializes a new instance of the class. + /// Constructs new target font modification information. + /// + /// Name of the font to write to ImGui font information. + /// Whether and how to use AXIS fonts. + /// Target font size in pixels, which will not be considered for further scaling. + /// Font scale to be referred for loading AXIS font of appropriate size. + internal TargetFontModification(string name, AxisMode axis, float sizePx, float globalFontScale) + { + this.Name = name; + this.Axis = axis; + this.TargetSizePx = sizePx; + this.Scale = globalFontScale; + this.SourceAxis = Service.Get().NewFontRef(new(GameFontFamily.Axis, this.TargetSizePx * this.Scale)); + } + + internal enum AxisMode + { + Suppress, + GameGlyphsOnly, + Overwrite, + } + + internal string Name { get; private init; } + + internal AxisMode Axis { get; private init; } + + internal float TargetSizePx { get; private init; } + + internal float Scale { get; private init; } + + internal GameFontHandle? SourceAxis { get; private init; } + + internal bool SourceAxisAvailable => this.SourceAxis != null && this.SourceAxis.ImFont.NativePtr != null; + + public void Dispose() + { + this.SourceAxis?.Dispose(); + } + } } 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 5940a42a3..ff42e93f3 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs @@ -4,138 +4,137 @@ 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) - { - // TODO: Needs to be updated for ImGui 1.88 - 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) + { + // TODO: Needs to be updated for ImGui 1.88 + return; #pragma warning disable CS0162 - 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}"); - } - } -#pragma warning restore CS0162 - } - - private static void ShowAssert(string source, string message) + if (!AssertsEnabled) { - 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; + 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}"); + } + } +#pragma warning restore CS0162 + } + + 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; } + /// - /// A snapshot of various ImGui context properties. + /// Gets the ImGui color stack size. /// - public class ImGuiContextSnapshot - { - /// - /// Gets the ImGui style var stack size. - /// - public int StyleVarStackSize { get; init; } + public int ColorStackSize { 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 font stack size. - /// - public int FontStackSize { get; init; } + /// + /// Gets the ImGui begin popup stack size. + /// + public int BeginPopupStackSize { 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; } - } + /// + /// 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 f1318a7fe..e941db7a4 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs @@ -7,312 +7,311 @@ 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. +/// +[ServiceManager.EarlyLoadedService] +internal class NotificationManager : IServiceType { /// - /// Class handling notifications/toasts in ImGui. - /// Ported from https://github.com/patrickcjk/imgui-notify. + /// Value indicating the bottom-left X padding. /// - [ServiceManager.EarlyLoadedService] - internal class NotificationManager : IServiceType + 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(); + + [ServiceManager.ServiceConstructor] + private NotificationManager() { - /// - /// 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(); - - [ServiceManager.ServiceConstructor] - private NotificationManager() + /// + /// 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 { - } + Content = content, + Title = title, + NotificationType = type, + DurationMs = msDelay, + }); + } - /// - /// 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) + /// + /// 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++) { - this.notifications.Add(new Notification + var tn = this.notifications.ElementAt(i); + + if (tn.GetPhase() == Notification.Phase.Expired) { - Content = content, - Title = title, - NotificationType = type, - DurationMs = msDelay, - }); - } + this.notifications.RemoveAt(i); + continue; + } - /// - /// Draw all currently queued notifications. - /// - public void Draw() - { - var viewportSize = ImGuiHelpers.MainViewport.Size; - var height = 0f; + var opacity = tn.GetFadePercent(); - for (var i = 0; i < this.notifications.Count; i++) + var iconColor = tn.Color; + iconColor.W = opacity; + + 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); + + ImGui.PushTextWrapPos(viewportSize.X / 3.0f); + + var wasTitleRendered = false; + + if (!tn.Icon.IsNullOrEmpty()) { - var tn = this.notifications.ElementAt(i); + wasTitleRendered = true; + ImGui.PushFont(InterfaceManager.IconFont); + ImGui.TextColored(iconColor, tn.Icon); + ImGui.PopFont(); + } - if (tn.GetPhase() == Notification.Phase.Expired) - { - this.notifications.RemoveAt(i); - continue; - } + var textColor = ImGuiColors.DalamudWhite; + textColor.W = opacity; - var opacity = tn.GetFadePercent(); - - var iconColor = tn.Color; - iconColor.W = opacity; - - 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); - - ImGui.PushTextWrapPos(viewportSize.X / 3.0f); - - var wasTitleRendered = false; + 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..1885ec809 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 cb7849c27..208e898f7 100644 --- a/Dalamud/Interface/Internal/PluginCategoryManager.cs +++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs @@ -5,416 +5,415 @@ 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 + 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(12, "special.dalamud", () => Locs.Category_Dalamud), + new(13, "special.plugins", () => Locs.Category_Plugins), + 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), + new(GroupKind.Changelog, () => Locs.Group_Changelog, 0, 12, 13), + + // order important, used for drawing, keep in sync with defaults for currentGroupIdx + }; + + private int currentGroupIdx = 2; + private int currentCategoryIdx = 0; + private bool isContentDirty; + + private Dictionary mapPluginCategories = new(); + private List highlightedCategoryIds = new(); + + /// + /// Type of category group. + /// + public enum GroupKind { /// - /// First categoryId for tag based categories. + /// UI group: dev mode only. /// - public const int FirstTagBasedCategoryId = 100; + DevTools, - private readonly CategoryInfo[] categoryList = + /// + /// UI group: installed plugins. + /// + Installed, + + /// + /// UI group: plugins that can be installed. + /// + Available, + + /// + /// UI group: changelog of plugins. + /// + Changelog, + } + + /// + /// 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 { - new(0, "special.all", () => Locs.Category_All), - new(10, "special.devInstalled", () => Locs.Category_DevInstalled), - new(11, "special.devIconTester", () => Locs.Category_IconTester), - new(12, "special.dalamud", () => Locs.Category_Dalamud), - new(13, "special.plugins", () => Locs.Category_Plugins), - 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), - new(GroupKind.Changelog, () => Locs.Group_Changelog, 0, 12, 13), - - // order important, used for drawing, keep in sync with defaults for currentGroupIdx - }; - - private int currentGroupIdx = 2; - private int currentCategoryIdx = 0; - private bool isContentDirty; - - private Dictionary mapPluginCategories = new(); - private List highlightedCategoryIds = new(); - - /// - /// Type of category group. - /// - public enum GroupKind - { - /// - /// UI group: dev mode only. - /// - DevTools, - - /// - /// UI group: installed plugins. - /// - Installed, - - /// - /// UI group: plugins that can be installed. - /// - Available, - - /// - /// UI group: changelog of plugins. - /// - Changelog, - } - - /// - /// 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("InstallerAllPlugins", "All Plugins"); - - public static string Group_Changelog => Loc.Localize("InstallerChangelog", "Changelog"); - - #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"); - - public static string Category_Plugins => Loc.Localize("InstallerCategoryPlugins", "Plugins"); - - public static string Category_Dalamud => Loc.Localize("InstallerCategoryDalamud", "Dalamud"); - - #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("InstallerAllPlugins", "All Plugins"); + + public static string Group_Changelog => Loc.Localize("InstallerChangelog", "Changelog"); + + #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"); + + public static string Category_Plugins => Loc.Localize("InstallerCategoryPlugins", "Plugins"); + + public static string Category_Dalamud => Loc.Localize("InstallerCategoryDalamud", "Dalamud"); + + #endregion + } } diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 99c6dd3d1..75c94a61d 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -10,591 +10,590 @@ using ImGuiNET; // Customised version of https://github.com/aers/FFXIVUIDebug -namespace Dalamud.Interface.Internal +namespace Dalamud.Interface.Internal; + +/// +/// This class displays a debug window to inspect native addons. +/// +internal unsafe class UiDebug { - /// - /// 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] { - private const int UnitListCount = 18; + "Depth Layer 1", + "Depth Layer 2", + "Depth Layer 3", + "Depth Layer 4", + "Depth Layer 5", + "Depth Layer 6", + "Depth Layer 7", + "Depth Layer 8", + "Depth Layer 9", + "Depth Layer 10", + "Depth Layer 11", + "Depth Layer 12", + "Depth Layer 13", + "Loaded Units", + "Focused Units", + "Units 16", + "Units 17", + "Units 18", + }; - private readonly GetAtkStageSingleton getAtkStageSingleton; - private readonly bool[] selectedInList = new bool[UnitListCount]; - private readonly string[] listNames = new string[UnitListCount] + private bool doingSearch; + private string searchInput = string.Empty; + private AtkUnitBase* selectedUnitBase = null; + + /// + /// 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) { - "Depth Layer 1", - "Depth Layer 2", - "Depth Layer 3", - "Depth Layer 4", - "Depth Layer 5", - "Depth Layer 6", - "Depth Layer 7", - "Depth Layer 8", - "Depth Layer 9", - "Depth Layer 10", - "Depth Layer 11", - "Depth Layer 12", - "Depth Layer 13", - "Loaded Units", - "Focused Units", - "Units 16", - "Units 17", - "Units 18", - }; - - private bool doingSearch; - private string searchInput = string.Empty; - private AtkUnitBase* selectedUnitBase = null; - - /// - /// 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) - { - ImGui.SameLine(); - ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true); - this.DrawUnitBase(this.selectedUnitBase); - ImGui.EndChild(); - } - - ImGui.PopStyleVar(); - } - - 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.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - 25); - if (ImGui.SmallButton("V")) - { - atkUnitBase->Flags ^= 0x20; - } + ImGui.PopStyleVar(); + } - ImGui.Separator(); - ImGuiHelpers.ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); - ImGuiHelpers.ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); - 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); - ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); - ImGui.Text($"Scale: {atkUnitBase->Scale * 100}%%"); - ImGui.Text($"Widget Count {atkUnitBase->UldManager.ObjectCount}"); + ImGui.Text($"{addonName}"); + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF); + ImGui.Text(isVisible ? "Visible" : "Not Visible"); + ImGui.PopStyleColor(); - ImGui.Separator(); + ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - 25); + if (ImGui.SmallButton("V")) + { + atkUnitBase->Flags ^= 0x20; + } - object addonObj = *atkUnitBase; + ImGui.Separator(); + ImGuiHelpers.ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); + ImGuiHelpers.ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); + ImGui.Separator(); - Util.ShowStruct(addonObj, (ulong)atkUnitBase); + 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; + + Util.ShowStruct(addonObj, (ulong)atkUnitBase); + + 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.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); + if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) { - 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(); + ImGui.PopStyleColor(); - for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) - { - this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); - } - - ImGui.TreePop(); - } - else + for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) { - ImGui.PopStyleColor(); + this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); } + + ImGui.TreePop(); } - } - - 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 "); + ImGui.PopStyleColor(); } } + } - private void PrintSimpleNode(AtkResNode* node, string treePrefix) + 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 popped = false; - var isVisible = (node->Flags & 0x10) == 0x10; + 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.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); + ImGui.PopStyleColor(); + popped = true; + } - if (isVisible) - { - ImGui.PopStyleColor(); - popped = true; - } + ImGui.Text("Node: "); + ImGui.SameLine(); + ImGuiHelpers.ClickToCopyText($"{(ulong)node:X}"); + ImGui.SameLine(); + switch (node->Type) + { + case NodeType.Text: Util.ShowStruct(*(AtkTextNode*)node, (ulong)node); break; + case NodeType.Image: Util.ShowStruct(*(AtkImageNode*)node, (ulong)node); break; + case NodeType.Collision: Util.ShowStruct(*(AtkCollisionNode*)node, (ulong)node); break; + case NodeType.NineGrid: Util.ShowStruct(*(AtkNineGridNode*)node, (ulong)node); break; + case NodeType.Counter: Util.ShowStruct(*(AtkCounterNode*)node, (ulong)node); break; + default: Util.ShowStruct(*node, (ulong)node); break; + } - ImGui.Text("Node: "); - ImGui.SameLine(); - ImGuiHelpers.ClickToCopyText($"{(ulong)node:X}"); - ImGui.SameLine(); - switch (node->Type) - { - case NodeType.Text: Util.ShowStruct(*(AtkTextNode*)node, (ulong)node); break; - case NodeType.Image: Util.ShowStruct(*(AtkImageNode*)node, (ulong)node); break; - case NodeType.Collision: Util.ShowStruct(*(AtkCollisionNode*)node, (ulong)node); break; - case NodeType.NineGrid: Util.ShowStruct(*(AtkNineGridNode*)node, (ulong)node); break; - case NodeType.Counter: Util.ShowStruct(*(AtkCounterNode*)node, (ulong)node); break; - default: Util.ShowStruct(*node, (ulong)node); break; - } + this.PrintResNode(node); - this.PrintResNode(node); + if (node->ChildNode != null) + this.PrintNode(node->ChildNode); - 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))}"); - 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.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($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); - int b = textNode->AlignmentFontType; - if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) + 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) { - 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(); - } - } - } + ImGui.Text("part id > part count?"); } else { - ImGui.Text("no texture loaded"); + 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(); + } + } } - - 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; - - var componentInfo = compNode->Component->UldManager; - - var childCount = componentInfo.NodeListCount; - - var objectInfo = (AtkUldComponentInfo*)componentInfo.Objects; - if (objectInfo == null) - { - return; - } - - if (isVisible) - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); - - 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(); - ImGuiHelpers.ClickToCopyText($"{(ulong)node:X}"); - ImGui.SameLine(); - Util.ShowStruct(*compNode, (ulong)compNode); - ImGui.Text("Component: "); - ImGui.SameLine(); - ImGuiHelpers.ClickToCopyText($"{(ulong)compNode->Component:X}"); - ImGui.SameLine(); - - switch (objectInfo->ComponentType) - { - case ComponentType.Button: Util.ShowStruct(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.Slider: Util.ShowStruct(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.Window: Util.ShowStruct(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.CheckBox: Util.ShowStruct(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.GaugeBar: Util.ShowStruct(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.RadioButton: Util.ShowStruct(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.TextInput: Util.ShowStruct(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component); break; - case ComponentType.Icon: Util.ShowStruct(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component); break; - default: Util.ShowStruct(*compNode->Component, (ulong)compNode->Component); 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++) + } + else { - this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); + ImGui.Text("no texture loaded"); } - ImGui.TreePop(); - } - else + 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; + + var componentInfo = compNode->Component->UldManager; + + var childCount = componentInfo.NodeListCount; + + var objectInfo = (AtkUldComponentInfo*)componentInfo.Objects; + if (objectInfo == null) + { + return; + } + + if (isVisible) + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); + + 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(); + ImGuiHelpers.ClickToCopyText($"{(ulong)node:X}"); + ImGui.SameLine(); + Util.ShowStruct(*compNode, (ulong)compNode); + ImGui.Text("Component: "); + ImGui.SameLine(); + ImGuiHelpers.ClickToCopyText($"{(ulong)compNode->Component:X}"); + ImGui.SameLine(); + + switch (objectInfo->ComponentType) + { + case ComponentType.Button: Util.ShowStruct(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.Slider: Util.ShowStruct(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.Window: Util.ShowStruct(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.CheckBox: Util.ShowStruct(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.GaugeBar: Util.ShowStruct(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.RadioButton: Util.ShowStruct(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.TextInput: Util.ShowStruct(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component); break; + case ComponentType.Icon: Util.ShowStruct(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component); break; + default: Util.ShowStruct(*compNode->Component, (ulong)compNode->Component); 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++) { - ImGui.PopStyleColor(); + this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); } ImGui.TreePop(); } - else if (ImGui.IsItemHovered()) + else { - this.DrawOutline(node); - } - - if (isVisible && !popped) ImGui.PopStyleColor(); + } + + ImGui.TreePop(); + } + else if (ImGui.IsItemHovered()) + { + this.DrawOutline(node); } - 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}"); + if (isVisible && !popped) ImGui.PopStyleColor(); + } - ImGui.SameLine(); - ImGui.TextDisabled($"C:{count} {ptr:X}"); - return treeNode; + private void PrintResNode(AtkResNode* node) + { + ImGui.Text($"NodeID: {node->NodeID}"); + ImGui.SameLine(); + if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) + { + node->Flags ^= 0x10; } - private void DrawUnitBaseList() + ImGui.SameLine(); + if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) { - var foundSelected = false; - var noResults = true; - var stage = this.getAtkStageSingleton(); + ImGui.SetClipboardText($"{(ulong)node:X}"); + } - var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList; + 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}"); + } - var searchStr = this.searchInput; - var searching = !string.IsNullOrEmpty(searchStr); + 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); + } - for (var i = 0; i < UnitListCount; i++) + 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) { - var headerDrawn = false; + headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); + headerDrawn = true; + noResults = false; + } - var highlight = this.selectedUnitBase != null && this.selectedInList[i]; - this.selectedInList[i] = false; - var unitManager = &unitManagers[i]; + 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 unitBaseArray = &unitManager->AtkUnitEntries; + var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); + if (searching) + { + if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; + } - var headerOpen = true; - - if (!searching) + noResults = false; + if (!headerDrawn) { headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); headerDrawn = true; - noResults = false; } - for (var j = 0; j < unitManager->Count && headerOpen; j++) + if (headerOpen) { - var unitBase = unitBaseArray[j]; - if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase) + 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.selectedInList[i] = true; + this.selectedUnitBase = unitBase; 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; } + + ImGui.PopStyleColor(); } } - if (noResults) + if (headerDrawn && headerOpen) { - ImGui.TextDisabled("No Results"); + ImGui.TreePop(); } - if (!foundSelected) + if (this.selectedInList[i] == false && this.selectedUnitBase != null) { - this.selectedUnitBase = null; - } - - if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) - { - this.doingSearch = false; - } - else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) - { - this.doingSearch = true; + for (var j = 0; j < unitManager->Count; j++) + { + if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue; + this.selectedInList[i] = true; + foundSelected = true; + } } } - private Vector2 GetNodePosition(AtkResNode* node) + if (noResults) { - 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; + ImGui.TextDisabled("No Results"); } - private Vector2 GetNodeScale(AtkResNode* node) + if (!foundSelected) { - 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; + this.selectedUnitBase = null; } - private bool GetNodeVisible(AtkResNode* node) + if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) { - if (node == null) return false; - while (node != null) - { - if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false; - node = node->ParentNode; - } - - return true; + this.doingSearch = false; } - - private void DrawOutline(AtkResNode* node) + else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) { - 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); + 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); + } } diff --git a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs index c035f0d6f..ea1f7cd95 100644 --- a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs @@ -8,161 +8,160 @@ using Dalamud.Utility; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// For major updates, an in-game Changelog window. +/// +internal sealed class ChangelogWindow : Window, IDisposable { /// - /// For major updates, an in-game Changelog window. + /// Whether the latest update warrants a changelog window. /// - internal sealed class ChangelogWindow : Window, IDisposable - { - /// - /// Whether the latest update warrants a changelog window. - /// - public const string WarrantsChangelogForMajorMinor = "7.0."; + public const string WarrantsChangelogForMajorMinor = "7.0."; - private const string ChangeLog = - @"• Updated Dalamud for compatibility with Patch 6.2 + private const string ChangeLog = + @"• Updated Dalamud for compatibility with Patch 6.2 • Made things more speedy • Plugins can now be toggled off while remaining installed, instead of being removed completely If you note any issues or need help, please check the FAQ, and reach out on our Discord if you need help. Thanks and have fun!"; - 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; - private readonly TextureWrap logoTexture; + private readonly TextureWrap logoTexture; - /// - /// Initializes a new instance of the class. - /// - public ChangelogWindow() - : base("What's new in Dalamud?", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize) + /// + /// Initializes a new instance of the class. + /// + public ChangelogWindow() + : base("What's new in Dalamud?", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize) + { + this.Namespace = "DalamudChangelogWindow"; + + this.Size = new Vector2(885, 463); + this.SizeCondition = ImGuiCond.Appearing; + + var interfaceManager = Service.Get(); + var dalamud = Service.Get(); + + this.logoTexture = + interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"))!; + } + + /// + public override void Draw() + { + ImGui.Text($"Dalamud has been updated to version D{this.assemblyVersion}."); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("The following changes were introduced:"); + + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(0); + var imgCursor = ImGui.GetCursorPos(); + + 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; - - var interfaceManager = Service.Get(); - var dalamud = Service.Get(); - - this.logoTexture = - interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"))!; + Service.Get().OpenPluginInstaller(); } - /// - public override void Draw() + if (ImGui.IsItemHovered()) { - ImGui.Text($"Dalamud has been updated to version D{this.assemblyVersion}."); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("The following changes were introduced:"); - - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(0); - var imgCursor = ImGui.GetCursorPos(); - - 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())) - { - Util.OpenLink("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())) - { - Util.OpenLink("https://goatcorp.github.io/faq/"); - } - - if (ImGui.IsItemHovered()) - { - ImGui.PopFont(); - ImGui.SetTooltip("See the FAQ"); - ImGui.PushFont(UiBuilder.IconFont); - } - - ImGui.SameLine(); - - if (ImGui.Button(FontAwesomeIcon.Heart.ToIconString())) - { - Util.OpenLink("https://goatcorp.github.io/faq/support"); - } - - if (ImGui.IsItemHovered()) - { - ImGui.PopFont(); - ImGui.SetTooltip("Support what we care about"); - ImGui.PushFont(UiBuilder.IconFont); - } - ImGui.PopFont(); - - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(20, 0); - ImGui.SameLine(); - - if (ImGui.Button("Close")) - { - this.IsOpen = false; - } - - imgCursor.X += 750; - imgCursor.Y -= 30; - ImGui.SetCursorPos(imgCursor); - - ImGui.Image(this.logoTexture.ImGuiHandle, new Vector2(100)); + ImGui.SetTooltip("Open Plugin Installer"); + ImGui.PushFont(UiBuilder.IconFont); } - /// - /// Dispose this window. - /// - public void Dispose() + ImGui.SameLine(); + + if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) { - this.logoTexture.Dispose(); + Util.OpenLink("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())) + { + Util.OpenLink("https://goatcorp.github.io/faq/"); + } + + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("See the FAQ"); + ImGui.PushFont(UiBuilder.IconFont); + } + + ImGui.SameLine(); + + if (ImGui.Button(FontAwesomeIcon.Heart.ToIconString())) + { + Util.OpenLink("https://goatcorp.github.io/faq/support"); + } + + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("Support what we care about"); + ImGui.PushFont(UiBuilder.IconFont); + } + + ImGui.PopFont(); + + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(20, 0); + ImGui.SameLine(); + + if (ImGui.Button("Close")) + { + this.IsOpen = false; + } + + imgCursor.X += 750; + imgCursor.Y -= 30; + ImGui.SetCursorPos(imgCursor); + + ImGui.Image(this.logoTexture.ImGuiHandle, new Vector2(100)); + } + + /// + /// Dispose this window. + /// + public void Dispose() + { + this.logoTexture.Dispose(); } } diff --git a/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs index aeabf5e5f..3d2b585ac 100644 --- a/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs @@ -7,60 +7,59 @@ 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.colors = new List<(string Name, Vector4 Color)>() { - this.Size = new Vector2(600, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; + ("DalamudRed", ImGuiColors.DalamudRed), + ("DalamudGrey", ImGuiColors.DalamudGrey), + ("DalamudGrey2", ImGuiColors.DalamudGrey2), + ("DalamudGrey3", ImGuiColors.DalamudGrey3), + ("DalamudWhite", ImGuiColors.DalamudWhite), + ("DalamudWhite2", ImGuiColors.DalamudWhite2), + ("DalamudOrange", ImGuiColors.DalamudOrange), + ("DalamudYellow", ImGuiColors.DalamudYellow), + ("DalamudViolet", ImGuiColors.DalamudViolet), + ("TankBlue", ImGuiColors.TankBlue), + ("HealerGreen", ImGuiColors.HealerGreen), + ("DPSRed", ImGuiColors.DPSRed), + ("ParsedGrey", ImGuiColors.ParsedGrey), + ("ParsedGreen", ImGuiColors.ParsedGreen), + ("ParsedBlue", ImGuiColors.ParsedBlue), + ("ParsedPurple", ImGuiColors.ParsedPurple), + ("ParsedOrange", ImGuiColors.ParsedOrange), + ("ParsedPink", ImGuiColors.ParsedPink), + ("ParsedGold", ImGuiColors.ParsedGold), + }.OrderBy(colorDemo => colorDemo.Name).ToList(); + } - this.colors = new List<(string Name, Vector4 Color)>() - { - ("DalamudRed", ImGuiColors.DalamudRed), - ("DalamudGrey", ImGuiColors.DalamudGrey), - ("DalamudGrey2", ImGuiColors.DalamudGrey2), - ("DalamudGrey3", ImGuiColors.DalamudGrey3), - ("DalamudWhite", ImGuiColors.DalamudWhite), - ("DalamudWhite2", ImGuiColors.DalamudWhite2), - ("DalamudOrange", ImGuiColors.DalamudOrange), - ("DalamudYellow", ImGuiColors.DalamudYellow), - ("DalamudViolet", ImGuiColors.DalamudViolet), - ("TankBlue", ImGuiColors.TankBlue), - ("HealerGreen", ImGuiColors.HealerGreen), - ("DPSRed", ImGuiColors.DPSRed), - ("ParsedGrey", ImGuiColors.ParsedGrey), - ("ParsedGreen", ImGuiColors.ParsedGreen), - ("ParsedBlue", ImGuiColors.ParsedBlue), - ("ParsedPurple", ImGuiColors.ParsedPurple), - ("ParsedOrange", ImGuiColors.ParsedOrange), - ("ParsedPink", ImGuiColors.ParsedPink), - ("ParsedGold", ImGuiColors.ParsedGold), - }.OrderBy(colorDemo => colorDemo.Name).ToList(); - } + /// + public override void Draw() + { + ImGui.Text("This is a collection of UI colors you can use in your plugin."); - /// - public override void Draw() + 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..638b30e66 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 +namespace Dalamud.Interface.Internal.Windows; + +/// +/// Component Demo Window to view custom ImGui components. +/// +internal sealed class ComponentDemoWindow : Window { - /// - /// 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[] { - private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700); + 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 readonly List<(string Name, Action Demo)> componentDemos; - private readonly IReadOnlyList easings = new Easing[] + private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds; + private Vector4 defaultColor = ImGuiColors.DalamudOrange; + + /// + /// 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() { - 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), + ("Test", ImGuiComponents.Test), + ("HelpMarker", HelpMarkerDemo), + ("IconButton", IconButtonDemo), + ("TextWithLabel", TextWithLabelDemo), + ("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo), }; + } - private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds; - private Vector4 defaultColor = ImGuiColors.DalamudOrange; - - /// - /// Initializes a new instance of the class. - /// - public ComponentDemoWindow() - : base("Dalamud Components Demo") + /// + public override void OnOpen() + { + foreach (var easing in this.easings) { - this.Size = new Vector2(600, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; + easing.Restart(); + } + } - this.RespectCloseHotkey = false; + /// + public override void OnClose() + { + foreach (var easing in this.easings) + { + easing.Stop(); + } + } - this.componentDemos = new() + /// + 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 5a233b37a..ffa3323dd 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -17,570 +17,569 @@ 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[] { "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 int levelFilter; + private List sourceFilters = new(); + private bool filterShowUncaughtExceptions = false; + private bool isFiltered = false; + + private int historyPos; + private List history = new(); + + private bool killGameArmed = false; + /// - /// 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[] { "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 int levelFilter; - private List sourceFilters = new(); - private bool filterShowUncaughtExceptions = false; - private bool isFiltered = false; + /// + public override void OnOpen() + { + this.killGameArmed = false; + base.OnOpen(); + } - private int historyPos; - private List history = new(); + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + SerilogEventSink.Instance.LogLine -= this.OnLogLine; + } - private bool killGameArmed = false; + /// + /// Clear the window of all log entries. + /// + public void Clear() + { + lock (this.renderLock) + { + this.logText.Clear(); + this.filteredLogText.Clear(); + } + } - /// - /// Initializes a new instance of the class. - /// - public ConsoleWindow() - : base("Dalamud Console") + /// + /// Add a single log line to the display. + /// + /// The line to add. + /// The Serilog event associated with this line. + public void HandleLogLine(string line, LogEvent logEvent) + { + if (line.IndexOfAny(new[] { '\n', '\r' }) != -1) + { + var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); + + this.AddAndFilter(subLines[0], logEvent, false); + + for (var i = 1; i < subLines.Length; i++) + { + this.AddAndFilter(subLines[i], logEvent, true); + } + } + else + { + this.AddAndFilter(line, logEvent, false); + } + } + + /// + public override void Draw() + { + // Options menu + if (ImGui.BeginPopup("Options")) { 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; - } - - private List LogEntries => this.isFiltered ? this.filteredLogText : this.logText; - - /// - public override void OnOpen() - { - this.killGameArmed = false; - base.OnOpen(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - SerilogEventSink.Instance.LogLine -= this.OnLogLine; - } - - /// - /// Clear the window of all log entries. - /// - public void Clear() - { - lock (this.renderLock) + if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) { - this.logText.Clear(); - this.filteredLogText.Clear(); - } - } - - /// - /// Add a single log line to the display. - /// - /// The line to add. - /// The Serilog event associated with this line. - public void HandleLogLine(string line, LogEvent logEvent) - { - if (line.IndexOfAny(new[] { '\n', '\r' }) != -1) - { - var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); - - this.AddAndFilter(subLines[0], logEvent, false); - - for (var i = 1; i < subLines.Length; i++) - { - this.AddAndFilter(subLines[i], logEvent, true); - } - } - else - { - this.AddAndFilter(line, logEvent, false); - } - } - - /// - public override void Draw() - { - // Options menu - if (ImGui.BeginPopup("Options")) - { - 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)EntryPoint.LogLevelSwitch.MinimumLevel; - if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast().Select(x => x.ToString()).ToArray(), 6)) - { - EntryPoint.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel; - configuration.LogLevel = (LogEventLevel)prevLevel; - configuration.Save(); - } - - ImGui.EndPopup(); + configuration.LogAutoScroll = this.autoScroll; + configuration.Save(); } - // Filter menu - if (ImGui.BeginPopup("Filters")) + if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) { - if (ImGui.Checkbox("Enabled", ref this.isFiltered)) - { - this.Refilter(); - } + configuration.LogOpenAtStartup = this.openAtStartup; + configuration.Save(); + } - if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue)) - { - this.Refilter(); - } + var prevLevel = (int)EntryPoint.LogLevelSwitch.MinimumLevel; + if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast().Select(x => x.ToString()).ToArray(), 6)) + { + EntryPoint.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel; + configuration.LogLevel = (LogEventLevel)prevLevel; + configuration.Save(); + } - ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm."); + ImGui.EndPopup(); + } - if (ImGui.BeginCombo("Levels", this.levelFilter == 0 ? "All Levels..." : "Selected Levels...")) + // Filter menu + if (ImGui.BeginPopup("Filters")) + { + if (ImGui.Checkbox("Enabled", ref this.isFiltered)) + { + this.Refilter(); + } + + if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + this.Refilter(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm."); + + if (ImGui.BeginCombo("Levels", this.levelFilter == 0 ? "All Levels..." : "Selected Levels...")) + { + for (var i = 0; i < this.logLevelStrings.Length; i++) { - for (var i = 0; i < this.logLevelStrings.Length; i++) + if (ImGui.Selectable(this.logLevelStrings[i], ((this.levelFilter >> i) & 1) == 1)) { - if (ImGui.Selectable(this.logLevelStrings[i], ((this.levelFilter >> i) & 1) == 1)) - { - this.levelFilter ^= 1 << i; - this.Refilter(); - } + this.levelFilter ^= 1 << i; + this.Refilter(); } - - ImGui.EndCombo(); } - // Filter by specific plugin(s) - var pluginInternalNames = Service.Get().InstalledPlugins - .Select(p => p.Manifest.InternalName) - .OrderBy(s => s).ToList(); - var sourcePreviewVal = this.sourceFilters.Count switch + ImGui.EndCombo(); + } + + // Filter by specific plugin(s) + var pluginInternalNames = Service.Get().InstalledPlugins + .Select(p => p.Manifest.InternalName) + .OrderBy(s => s).ToList(); + var sourcePreviewVal = this.sourceFilters.Count switch + { + 0 => "All plugins...", + 1 => "1 plugin...", + _ => $"{this.sourceFilters.Count} plugins...", + }; + var sourceSelectables = pluginInternalNames.Union(this.sourceFilters).ToList(); + if (ImGui.BeginCombo("Plugins", sourcePreviewVal)) + { + foreach (var selectable in sourceSelectables) { - 0 => "All plugins...", - 1 => "1 plugin...", - _ => $"{this.sourceFilters.Count} plugins...", - }; - var sourceSelectables = pluginInternalNames.Union(this.sourceFilters).ToList(); - if (ImGui.BeginCombo("Plugins", sourcePreviewVal)) - { - foreach (var selectable in sourceSelectables) + if (ImGui.Selectable(selectable, this.sourceFilters.Contains(selectable))) { - if (ImGui.Selectable(selectable, this.sourceFilters.Contains(selectable))) + if (!this.sourceFilters.Contains(selectable)) { - if (!this.sourceFilters.Contains(selectable)) - { - this.sourceFilters.Add(selectable); - } - else - { - this.sourceFilters.Remove(selectable); - } - - this.Refilter(); + this.sourceFilters.Add(selectable); } + else + { + this.sourceFilters.Remove(selectable); + } + + this.Refilter(); } - - ImGui.EndCombo(); } - if (ImGui.Checkbox("Always Show Uncaught Exceptions", ref this.filterShowUncaughtExceptions)) + ImGui.EndCombo(); + } + + if (ImGui.Checkbox("Always Show Uncaught Exceptions", ref this.filterShowUncaughtExceptions)) + { + 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 (this.killGameArmed) + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.Flushed)) + Process.GetCurrentProcess().Kill(); + } + else + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull)) + this.killGameArmed = true; + } + + 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.Refilter(); - } + var line = this.LogEntries[i]; - ImGui.EndPopup(); - } + if (!line.IsMultiline) + ImGui.Separator(); - ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level)); + ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level)); + ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level)); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) - ImGui.OpenPopup("Options"); + ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns); + ImGui.SameLine(); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Options"); + ImGui.PopStyleColor(3); - 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 (this.killGameArmed) - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.Flushed)) - Process.GetCurrentProcess().Kill(); - } - else - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull)) - this.killGameArmed = true; - } - - 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++) + if (!line.IsMultiline) { - 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.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.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.SetCursorPosX(cursorLogLine); + ImGui.TextUnformatted(line.Line); + } } - ImGui.PopFont(); + clipper.End(); + } - ImGui.PopStyleVar(); + ImGui.PopFont(); - if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) + 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.SetScrollHereY(1.0f); + ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f)); } - - // 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) + else { - 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.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f)); } + } - ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80); + 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")) + var getFocus = false; + unsafe + { + 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, LogEvent logEvent, 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 = logEvent.Level, - Line = line, - TimeStamp = logEvent.Timestamp, - HasException = logEvent.Exception != null, - }; - - if (logEvent.Properties.TryGetValue("SourceContext", out var sourceProp) && - sourceProp is ScalarValue { Value: string value }) - { - entry.Source = value; - } - - this.logText.Add(entry); - - if (!this.isFiltered) - return; - - if (this.IsFilterApplicable(entry)) - this.filteredLogText.Add(entry); - } - - private bool IsFilterApplicable(LogEntry entry) - { - if (this.levelFilter > 0 && ((this.levelFilter >> (int)entry.Level) & 1) == 0) - return false; - - // Show exceptions that weren't properly tagged with a Source (generally meaning they were uncaught) - // After log levels because uncaught exceptions should *never* fall below Error. - if (this.filterShowUncaughtExceptions && entry.HasException && entry.Source == null) - return true; - - if (this.sourceFilters.Count > 0 && !this.sourceFilters.Contains(entry.Source)) - return false; - - if (!string.IsNullOrEmpty(this.textFilter) && !entry.Line.Contains(this.textFilter)) - return false; - - 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, LogEvent LogEvent) logEvent) - { - this.HandleLogLine(logEvent.Line, logEvent.LogEvent); - } - - private class LogEntry - { - public string Line { get; set; } - - public LogEventLevel Level { get; set; } - - public DateTimeOffset TimeStamp { get; set; } - - public bool IsMultiline { get; set; } - - public string? Source { get; set; } - - public bool HasException { 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, LogEvent logEvent, bool isMultiline) + { + if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:")) + return; + + var entry = new LogEntry + { + IsMultiline = isMultiline, + Level = logEvent.Level, + Line = line, + TimeStamp = logEvent.Timestamp, + HasException = logEvent.Exception != null, + }; + + if (logEvent.Properties.TryGetValue("SourceContext", out var sourceProp) && + sourceProp is ScalarValue { Value: string value }) + { + entry.Source = value; + } + + this.logText.Add(entry); + + if (!this.isFiltered) + return; + + if (this.IsFilterApplicable(entry)) + this.filteredLogText.Add(entry); + } + + private bool IsFilterApplicable(LogEntry entry) + { + if (this.levelFilter > 0 && ((this.levelFilter >> (int)entry.Level) & 1) == 0) + return false; + + // Show exceptions that weren't properly tagged with a Source (generally meaning they were uncaught) + // After log levels because uncaught exceptions should *never* fall below Error. + if (this.filterShowUncaughtExceptions && entry.HasException && entry.Source == null) + return true; + + if (this.sourceFilters.Count > 0 && !this.sourceFilters.Contains(entry.Source)) + return false; + + if (!string.IsNullOrEmpty(this.textFilter) && !entry.Line.Contains(this.textFilter)) + return false; + + 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, LogEvent LogEvent) logEvent) + { + this.HandleLogLine(logEvent.Line, logEvent.LogEvent); + } + + private class LogEntry + { + public string Line { get; set; } + + public LogEventLevel Level { get; set; } + + public DateTimeOffset TimeStamp { get; set; } + + public bool IsMultiline { get; set; } + + public string? Source { get; set; } + + public bool HasException { get; set; } + } } diff --git a/Dalamud/Interface/Internal/Windows/CreditsWindow.cs b/Dalamud/Interface/Internal/Windows/CreditsWindow.cs index 9051642ca..83857a826 100644 --- a/Dalamud/Interface/Internal/Windows/CreditsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/CreditsWindow.cs @@ -12,16 +12,16 @@ using Dalamud.Utility; 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 ThankYouText = "Thank you!"; - private const string CreditsTextTempl = @" + private const float CreditFps = 60.0f; + private const string ThankYouText = "Thank you!"; + private const string CreditsTextTempl = @" Dalamud A FFXIV Plugin Framework Version D{0} @@ -158,170 +158,169 @@ Dalamud is licensed under AGPL v3 or later Contribute at: https://github.com/goatsoft/Dalamud "; - private readonly TextureWrap logoTexture; - private readonly Stopwatch creditsThrottler; + private readonly TextureWrap logoTexture; + private readonly Stopwatch creditsThrottler; - private string creditsText; + private string creditsText; - private GameFontHandle? thankYouFont; + private GameFontHandle? thankYouFont; - /// - /// Initializes a new instance of the class. - /// - public CreditsWindow() - : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar, true) + /// + /// Initializes a new instance of the class. + /// + public CreditsWindow() + : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoTitleBar, 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, Util.GetGitHashClientStructs()); + + Service.Get().SetBgm(833); + this.creditsThrottler.Restart(); + + if (this.thankYouFont == null) { - 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, Util.GetGitHashClientStructs()); - - Service.Get().SetBgm(833); - this.creditsThrottler.Restart(); - - if (this.thankYouFont == null) - { - var gfm = Service.Get(); - this.thankYouFont = gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.TrumpGothic34)); - } - } - - /// - 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, windowSize.Y + 20f); - ImGui.Text(string.Empty); - - const float imageSize = 190f; - ImGui.SameLine((ImGui.GetWindowWidth() / 2) - (imageSize / 2)); - ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(imageSize)); - - ImGuiHelpers.ScaledDummy(0, 20f); - - var windowX = ImGui.GetWindowSize().X; - - foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) - { - var lineLenX = ImGui.CalcTextSize(creditsLine).X; - - ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); - ImGui.SameLine(); - ImGui.TextUnformatted(creditsLine); - } - - ImGuiHelpers.ScaledDummy(0, 40f); - - if (this.thankYouFont != null) - { - ImGui.PushFont(this.thankYouFont.ImFont); - var thankYouLenX = ImGui.CalcTextSize(ThankYouText).X; - - ImGui.Dummy(new Vector2((windowX / 2) - (thankYouLenX / 2), 0f)); - ImGui.SameLine(); - ImGui.TextUnformatted(ThankYouText); - - ImGui.PopFont(); - } - - ImGuiHelpers.ScaledDummy(0, windowSize.Y + 50f); - - 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); - } - else - { - ImGui.SetScrollY(0); - } - } - - ImGui.EndChild(); - - ImGui.SetCursorPos(new Vector2(0)); - ImGui.BeginChild("button", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); - - var closeButtonSize = new Vector2(30); - ImGui.PushFont(InterfaceManager.IconFont); - ImGui.SetCursorPos(new Vector2(windowSize.X - closeButtonSize.X - 5, 5)); - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); - - if (ImGui.Button(FontAwesomeIcon.Times.ToIconString(), closeButtonSize)) - { - this.IsOpen = false; - } - - ImGui.PopStyleColor(3); - ImGui.PopFont(); - ImGui.EndChild(); - } - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.logoTexture?.Dispose(); - this.thankYouFont?.Dispose(); + var gfm = Service.Get(); + this.thankYouFont = gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.TrumpGothic34)); } } + + /// + 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, windowSize.Y + 20f); + ImGui.Text(string.Empty); + + const float imageSize = 190f; + ImGui.SameLine((ImGui.GetWindowWidth() / 2) - (imageSize / 2)); + ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(imageSize)); + + ImGuiHelpers.ScaledDummy(0, 20f); + + var windowX = ImGui.GetWindowSize().X; + + foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) + { + var lineLenX = ImGui.CalcTextSize(creditsLine).X; + + ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); + ImGui.SameLine(); + ImGui.TextUnformatted(creditsLine); + } + + ImGuiHelpers.ScaledDummy(0, 40f); + + if (this.thankYouFont != null) + { + ImGui.PushFont(this.thankYouFont.ImFont); + var thankYouLenX = ImGui.CalcTextSize(ThankYouText).X; + + ImGui.Dummy(new Vector2((windowX / 2) - (thankYouLenX / 2), 0f)); + ImGui.SameLine(); + ImGui.TextUnformatted(ThankYouText); + + ImGui.PopFont(); + } + + ImGuiHelpers.ScaledDummy(0, windowSize.Y + 50f); + + 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); + } + else + { + ImGui.SetScrollY(0); + } + } + + ImGui.EndChild(); + + ImGui.SetCursorPos(new Vector2(0)); + ImGui.BeginChild("button", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); + + var closeButtonSize = new Vector2(30); + ImGui.PushFont(InterfaceManager.IconFont); + ImGui.SetCursorPos(new Vector2(windowSize.X - closeButtonSize.X - 5, 5)); + ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); + + if (ImGui.Button(FontAwesomeIcon.Times.ToIconString(), closeButtonSize)) + { + this.IsOpen = false; + } + + ImGui.PopStyleColor(3); + ImGui.PopFont(); + ImGui.EndChild(); + } + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.logoTexture?.Dispose(); + this.thankYouFont?.Dispose(); + } } diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index c9e76e18a..8cb542b81 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -44,594 +44,645 @@ 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 bool isExcept; + 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; + + // DTR + private DtrBarEntry? dtrTest1; + private DtrBarEntry? dtrTest2; + private DtrBarEntry? dtrTest3; + + // Task Scheduler + private CancellationTokenSource taskSchedCancelSource = new(); + + 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 bool isExcept; - 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, + Aetherytes, + Dtr_Bar, + } - 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; - - // DTR - private DtrBarEntry? dtrTest1; - private DtrBarEntry? dtrTest2; - private DtrBarEntry? dtrTest3; - - // Task Scheduler - private CancellationTokenSource taskSchedCancelSource = new(); - - 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, - Aetherytes, - Dtr_Bar, - } - - /// - 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, - }; + switch (this.currentKind) + { + case DataKind.Server_OpCode: + this.DrawServerOpCode(); + break; - dataKind = dataKind.Replace(" ", string.Empty).ToLower(); + case DataKind.Address: + this.DrawAddress(); + break; - var matched = Enum.GetValues() - .Where(kind => Enum.GetName(kind).Replace("_", string.Empty).ToLower() == dataKind) - .FirstOrDefault(); + case DataKind.Object_Table: + this.DrawObjectTable(); + break; - if (matched != default) - { - this.currentKind = matched; + 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; + + case DataKind.Aetherytes: + this.DrawAetherytes(); + break; + + case DataKind.Dtr_Bar: + this.DrawDtr(); + break; + } } else { - Service.Get().PrintError($"/xldata: Invalid data type {dataKind}"); + ImGui.TextUnformatted("Data not ready."); + } + + this.isExcept = false; + } + catch (Exception ex) + { + if (!this.isExcept) + { + Log.Error(ex, "Could not draw data"); + } + + this.isExcept = true; + + 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); } } - /// - /// Draw the window via ImGui. - /// - public override void Draw() + 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) { - 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)) + ImGui.TextUnformatted($"{debugScannedValue.Key}"); + foreach (var valueTuple in debugScannedValue.Value) { - this.currentKind = (DataKind)currentKindIndex; + 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 += $"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 += $"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.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 + ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject.ObjectId}"); + if (this.resolveObjects) { - if (this.wasReady) + var actor = member.GameObject; + if (actor == null) { - 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; - - case DataKind.Aetherytes: - this.DrawAetherytes(); - break; - - case DataKind.Dtr_Bar: - this.DrawDtr(); - break; - } + ImGui.Text("Actor was null"); } else { - ImGui.TextUnformatted("Data not ready."); - } - - this.isExcept = false; - } - catch (Exception ex) - { - if (!this.isExcept) - { - Log.Error(ex, "Could not draw data"); - } - - this.isExcept = true; - - 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")); + this.PrintGameObject(actor, "-"); } } } + } - private void DrawObjectTable() + private void DrawBuddyList() + { + var buddyList = Service.Get(); + + ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); + + ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); { - 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) + var member = buddyList.CompanionBuddy; + if (member == null) { - ImGui.TextUnformatted("LocalPlayer null."); + ImGui.Text("[Companion] null"); } else { - 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 += $"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}"); + ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); if (this.resolveObjects) { - var actor = member.GameObject; - if (actor == null) + var gameObject = member.GameObject; + if (gameObject == null) { - ImGui.Text("Actor was null"); + ImGui.Text("GameObject was null"); } else { - this.PrintGameObject(actor, "-"); + this.PrintGameObject(gameObject, "-"); } } } } - private void DrawBuddyList() { - var buddyList = Service.Get(); - - ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); - - ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); + var member = buddyList.PetBuddy; + if (member == null) { - var member = buddyList.CompanionBuddy; - if (member == null) + ImGui.Text("[Pet] null"); + } + else + { + ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + if (this.resolveObjects) { - ImGui.Text("[Companion] null"); + var gameObject = member.GameObject; + if (gameObject == null) + { + ImGui.Text("GameObject was null"); + } + else + { + this.PrintGameObject(gameObject, "-"); + } } - else + } + } + + { + var count = buddyList.Length; + if (count == 0) + { + ImGui.Text("[BattleBuddy] None present"); + } + else + { + for (var i = 0; i < count; i++) { - ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + var member = buddyList[i]; + ImGui.Text($"[BattleBuddy] [{i}] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); if (this.resolveObjects) { var gameObject = member.GameObject; @@ -646,668 +697,617 @@ namespace Dalamud.Interface.Internal.Windows } } } + } + } - { - 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, "-"); - } - } - } - } + private void DrawPluginIPC() + { + if (this.ipcPub == null) + { + this.ipcPub = new CallGatePubSub("dataDemo1"); + this.ipcPub.RegisterAction((msg) => { - 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, "-"); - } - } - } - } - } + Log.Information($"Data action was called: {msg}"); + }); + + this.ipcPub.RegisterFunc((msg) => + { + Log.Information($"Data func was called: {msg}"); + return Guid.NewGuid().ToString(); + }); } - private void DrawPluginIPC() + if (this.ipcSub == null) { - if (this.ipcPub == null) + this.ipcSub = new CallGatePubSub("dataDemo1"); + this.ipcSub.Subscribe((msg) => { - 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) + Log.Information("PONG1"); + }); + this.ipcSub.Subscribe((msg) => { - 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")) + Log.Information("PONG2"); + }); + this.ipcSub.Subscribe((msg) => { - 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}"); + throw new Exception("PONG3"); + }); } - private void DrawCondition() + if (ImGui.Button("PING")) { - var condition = Service.Get(); + 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(); + ImGui.Text("Current Conditions:"); + ImGui.Separator(); - var didAny = false; + var didAny = false; - for (var i = 0; i < Condition.MaxConditionEntries; i++) - { - var typedCondition = (ConditionFlag)i; - var cond = condition[typedCondition]; + for (var i = 0; i < Condition.MaxConditionEntries; i++) + { + var typedCondition = (ConditionFlag)i; + var cond = condition[typedCondition]; - if (!cond) continue; + if (!cond) continue; - didAny = true; + 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!!!!!!!"); + ImGui.Text($"ID: {i} Enum: {typedCondition}"); } - private void DrawGauge() + 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) { - 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; - JobGaugeBase? gauge = jobID switch - { - 19 => jobGauges.Get(), - 20 => jobGauges.Get(), - 21 => jobGauges.Get(), - 22 => jobGauges.Get(), - 23 => jobGauges.Get(), - 24 => jobGauges.Get(), - 25 => jobGauges.Get(), - 27 => jobGauges.Get(), - 28 => jobGauges.Get(), - 30 => jobGauges.Get(), - 31 => jobGauges.Get(), - 32 => jobGauges.Get(), - 33 => jobGauges.Get(), - 34 => jobGauges.Get(), - 35 => jobGauges.Get(), - 37 => jobGauges.Get(), - 38 => jobGauges.Get(), - 39 => jobGauges.Get(), - 40 => jobGauges.Get(), - _ => null, - }; - - if (gauge == null) - { - ImGui.Text("No supported gauge exists for this job."); - return; - } - - Util.ShowObject(gauge); + ImGui.Text("Player is not present"); + return; } - private void DrawCommand() + var jobID = player.ClassJob.Id; + JobGaugeBase? gauge = jobID switch { - var commandManager = Service.Get(); + 19 => jobGauges.Get(), + 20 => jobGauges.Get(), + 21 => jobGauges.Get(), + 22 => jobGauges.Get(), + 23 => jobGauges.Get(), + 24 => jobGauges.Get(), + 25 => jobGauges.Get(), + 27 => jobGauges.Get(), + 28 => jobGauges.Get(), + 30 => jobGauges.Get(), + 31 => jobGauges.Get(), + 32 => jobGauges.Get(), + 33 => jobGauges.Get(), + 34 => jobGauges.Get(), + 35 => jobGauges.Get(), + 37 => jobGauges.Get(), + 38 => jobGauges.Get(), + 39 => jobGauges.Get(), + 40 => jobGauges.Get(), + _ => null, + }; - foreach (var command in commandManager.Commands) - { - ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n"); - } + if (gauge == null) + { + ImGui.Text("No supported gauge exists for this job."); + return; } - private unsafe void DrawAddon() + Util.ShowObject(gauge); + } + + private void DrawCommand() + { + var commandManager = Service.Get(); + + foreach (var command in commandManager.Commands) { - var gameGui = Service.Get(); + ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n"); + } + } - ImGui.InputText("Addon name", ref this.inputAddonName, 256); - ImGui.InputInt("Addon Index", ref this.inputAddonIndex); + private unsafe void DrawAddon() + { + var gameGui = Service.Get(); - if (this.inputAddonName.IsNullOrEmpty()) - return; + ImGui.InputText("Addon name", ref this.inputAddonName, 256); + ImGui.InputInt("Addon Index", ref this.inputAddonIndex); - var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); + if (this.inputAddonName.IsNullOrEmpty()) + return; - if (address == IntPtr.Zero) - { - ImGui.Text("Null"); - return; - } + var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); - 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")); - } + if (address == IntPtr.Zero) + { + ImGui.Text("Null"); + return; } - private void DrawAddonInspector() + 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.addonInspector ??= new UiDebug(); - this.addonInspector.Draw(); + this.findAgentInterfacePtr = gameGui.FindAgentInterface(address); } - private unsafe void DrawAtkArrayDataBrowser() + if (this.findAgentInterfacePtr != IntPtr.Zero) { - var fontWidth = ImGui.CalcTextSize("A").X; - var fontHeight = ImGui.GetTextLineHeightWithSpacing(); - var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule(); + ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():x}"); + ImGui.SameLine(); - if (uiModule == null) + 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}]")) { - 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)) { - 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.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.TableNextRow(); + ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; - if (numberArrayData != null) + if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) { - 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.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.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.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.TreePop(); + ImGui.EndTable(); } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); + + ImGui.TreePop(); } } - - ImGui.EndTable(); + else + { + ImGui.TextDisabled("--"); + ImGui.TableNextColumn(); + ImGui.TextDisabled("--"); + } } - ImGui.EndTabItem(); + ImGui.EndTable(); } - 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.EndTabItem(); + } - ImGui.EndTable(); + 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.TreePop(); + ImGui.EndTable(); } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); + + ImGui.TreePop(); } } - - ImGui.EndTable(); + else + { + ImGui.TextDisabled("--"); + ImGui.TableNextColumn(); + ImGui.TextDisabled("--"); + } } - ImGui.EndTabItem(); + ImGui.EndTable(); } - ImGui.EndTabBar(); + ImGui.EndTabItem(); } + + ImGui.EndTabBar(); } + } - private void DrawStartInfo() + 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) { - var startInfo = Service.Get(); + this.PrintGameObject(targetMgr.Target, "CurrentTarget"); - ImGui.Text(JsonConvert.SerializeObject(startInfo, Formatting.Indented)); - } + ImGui.Text("Target"); + Util.ShowGameObjectStruct(targetMgr.Target); - private void DrawTarget() - { - var clientState = Service.Get(); - var targetMgr = Service.Get(); - - if (targetMgr.Target != null) + var tot = targetMgr.Target.TargetObject; + if (tot != null) { - this.PrintGameObject(targetMgr.Target, "CurrentTarget"); - - ImGui.Text("Target"); - Util.ShowGameObjectStruct(targetMgr.Target); - - var tot = targetMgr.Target.TargetObject; - if (tot != null) - { - ImGuiHelpers.ScaledDummy(10); - - ImGui.Separator(); - ImGui.Text("ToT"); - Util.ShowGameObjectStruct(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."); - } + ImGui.Separator(); + ImGui.Text("ToT"); + Util.ShowGameObjectStruct(tot); } ImGuiHelpers.ScaledDummy(10); + } - if (this.debugTex != null) + 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 { - ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); - ImGuiHelpers.ScaledDummy(5); - Util.ShowObject(this.debugTex); + 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."); } } - private void DrawKeyState() + ImGuiHelpers.ScaledDummy(10); + + if (this.debugTex != null) { - var keyState = Service.Get(); + ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); + ImGuiHelpers.ScaledDummy(5); + Util.ShowObject(this.debugTex); + } + } - ImGui.Columns(4); + private void DrawKeyState() + { + var keyState = Service.Get(); - var i = 0; - foreach (var vkCode in keyState.GetValidVirtualKeys()) - { - var code = (int)vkCode; - var value = keyState[code]; + ImGui.Columns(4); - ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed); + var i = 0; + foreach (var vkCode in keyState.GetValidVirtualKeys()) + { + var code = (int)vkCode; + var value = keyState[code]; - ImGui.Text($"{vkCode} ({code})"); + ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed); - ImGui.PopStyleColor(); + ImGui.Text($"{vkCode} ({code})"); - i++; - if (i % 24 == 0) - ImGui.NextColumn(); - } + ImGui.PopStyleColor(); - ImGui.Columns(1); + i++; + if (i % 24 == 0) + ImGui.NextColumn(); } - private void DrawGamepad() + ImGui.Columns(1); + } + + private void DrawGamepad() + { + var gamepadState = Service.Get(); + + static void DrawHelper(string text, uint mask, Func resolve) { - var gamepadState = Service.Get(); + 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)} "); + } - 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}"); + ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}"); #if DEBUG if (ImGui.IsItemHovered()) @@ -1317,444 +1317,443 @@ namespace Dalamud.Interface.Internal.Windows 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} "); + 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(); } - private void DrawConfiguration() + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(10); + ImGui.SameLine(); + + if (ImGui.Button("Cancel using CancellationTokenSource")) { - var config = Service.Get(); - Util.ShowObject(config); + this.taskSchedCancelSource.Cancel(); + this.taskSchedCancelSource = new(); } - private void DrawTaskSched() + ImGui.Text("Run in any thread: "); + ImGui.SameLine(); + + if (ImGui.Button("Short Task.Run")) { - if (ImGui.Button("Clear list")) + Task.Run(() => { Thread.Sleep(500); }); + } + + ImGui.SameLine(); + + if (ImGui.Button("Task in task(Delay)")) + { + var token = this.taskSchedCancelSource.Token; + Task.Run(async () => await this.TestTaskInTaskDelay(token)); + } + + ImGui.SameLine(); + + if (ImGui.Button("Task in task(Sleep)")) + { + Task.Run(async () => await this.TestTaskInTaskSleep()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Faulting task")) + { + Task.Run(() => { - TaskTracker.Clear(); - } + Thread.Sleep(200); - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(10); - ImGui.SameLine(); + string a = null; + a.Contains("dalamud"); + }); + } - if (ImGui.Button("Cancel using CancellationTokenSource")) + ImGui.Text("Run in Framework.Update: "); + ImGui.SameLine(); + + if (ImGui.Button("ASAP")) + { + Task.Run(async () => await Service.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token)); + } + + ImGui.SameLine(); + + if (ImGui.Button("In 1s")) + { + Task.Run(async () => await Service.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token, delay: TimeSpan.FromSeconds(1))); + } + + ImGui.SameLine(); + + if (ImGui.Button("In 60f")) + { + Task.Run(async () => await Service.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token, delayTicks: 60)); + } + + ImGui.SameLine(); + + if (ImGui.Button("Error in 1s")) + { + Task.Run(async () => await Service.Get().RunOnTick(() => throw new Exception("Test Exception"), cancellationToken: this.taskSchedCancelSource.Token, delay: TimeSpan.FromSeconds(1))); + } + + ImGui.SameLine(); + + if (ImGui.Button("As long as it's in Framework Thread")) + { + Task.Run(async () => await Service.Get().RunOnFrameworkThread(() => { Log.Information("Task dispatched from non-framework.update thread"); })); + Service.Get().RunOnFrameworkThread(() => { Log.Information("Task dispatched from framework.update thread"); }).Wait(); + } + + if (ImGui.Button("Drown in tasks")) + { + var token = this.taskSchedCancelSource.Token; + Task.Run(() => { - this.taskSchedCancelSource.Cancel(); - this.taskSchedCancelSource = new(); - } - - ImGui.Text("Run in any thread: "); - ImGui.SameLine(); - - if (ImGui.Button("Short Task.Run")) - { - Task.Run(() => { Thread.Sleep(500); }); - } - - ImGui.SameLine(); - - if (ImGui.Button("Task in task(Delay)")) - { - var token = this.taskSchedCancelSource.Token; - Task.Run(async () => await this.TestTaskInTaskDelay(token)); - } - - ImGui.SameLine(); - - if (ImGui.Button("Task in task(Sleep)")) - { - Task.Run(async () => await this.TestTaskInTaskSleep()); - } - - ImGui.SameLine(); - - if (ImGui.Button("Faulting task")) - { - Task.Run(() => + for (var i = 0; i < 100; i++) { - Thread.Sleep(200); - - string a = null; - a.Contains("dalamud"); - }); - } - - ImGui.Text("Run in Framework.Update: "); - ImGui.SameLine(); - - if (ImGui.Button("ASAP")) - { - Task.Run(async () => await Service.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token)); - } - - ImGui.SameLine(); - - if (ImGui.Button("In 1s")) - { - Task.Run(async () => await Service.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token, delay: TimeSpan.FromSeconds(1))); - } - - ImGui.SameLine(); - - if (ImGui.Button("In 60f")) - { - Task.Run(async () => await Service.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token, delayTicks: 60)); - } - - ImGui.SameLine(); - - if (ImGui.Button("Error in 1s")) - { - Task.Run(async () => await Service.Get().RunOnTick(() => throw new Exception("Test Exception"), cancellationToken: this.taskSchedCancelSource.Token, delay: TimeSpan.FromSeconds(1))); - } - - ImGui.SameLine(); - - if (ImGui.Button("As long as it's in Framework Thread")) - { - Task.Run(async () => await Service.Get().RunOnFrameworkThread(() => { Log.Information("Task dispatched from non-framework.update thread"); })); - Service.Get().RunOnFrameworkThread(() => { Log.Information("Task dispatched from framework.update thread"); }).Wait(); - } - - if (ImGui.Button("Drown in tasks")) - { - var token = this.taskSchedCancelSource.Token; - Task.Run(() => - { - for (var i = 0; i < 100; i++) + token.ThrowIfCancellationRequested(); + Task.Run(() => { - token.ThrowIfCancellationRequested(); - Task.Run(() => + for (var i = 0; i < 100; i++) { - for (var i = 0; i < 100; i++) + token.ThrowIfCancellationRequested(); + Task.Run(() => { - token.ThrowIfCancellationRequested(); - Task.Run(() => + for (var i = 0; i < 100; i++) { - for (var i = 0; i < 100; i++) + token.ThrowIfCancellationRequested(); + Task.Run(() => { - token.ThrowIfCancellationRequested(); - Task.Run(() => + for (var i = 0; i < 100; i++) { - for (var i = 0; i < 100; i++) + token.ThrowIfCancellationRequested(); + Task.Run(async () => { - token.ThrowIfCancellationRequested(); - Task.Run(async () => + for (var i = 0; i < 100; i++) { - for (var i = 0; i < 100; i++) - { - token.ThrowIfCancellationRequested(); - await Task.Delay(1); - } - }); - } - }); - } - }); - } - }); - } - }); - } - - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(20); - - // Needed to init the task tracker, if we're not on a debug build - Service.Get().Enable(); - - 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); + token.ThrowIfCancellationRequested(); + await Task.Delay(1); + } + }); + } + }); + } + }); } - catch (Exception ex) - { - Log.Error(ex, "Could not cancel task."); - } - } + }); + } + }); + } - ImGuiHelpers.ScaledDummy(10); + ImGui.SameLine(); - ImGui.TextUnformatted(task.StackTrace.ToString()); + ImGuiHelpers.ScaledDummy(20); - if (task.Exception != null) + // Needed to init the task tracker, if we're not on a debug build + Service.Get().Enable(); + + 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 { - ImGuiHelpers.ScaledDummy(15); - ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:"); - ImGui.TextUnformatted(task.Exception.ToString()); + var cancelFunc = + typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance); + cancelFunc.Invoke(task, null); + } + catch (Exception ex) + { + Log.Error(ex, "Could not cancel task."); } } - else + + ImGuiHelpers.ScaledDummy(10); + + ImGui.TextUnformatted(task.StackTrace.ToString()); + + if (task.Exception != null) { - 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 void DrawAetherytes() - { - if (!ImGui.BeginTable("##aetheryteTable", 11, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) - return; - - ImGui.TableSetupScrollFreeze(0, 1); - ImGui.TableSetupColumn("Idx", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Zone", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Ward", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Plot", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Sub", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Gil", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Fav", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Shared", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Appartment", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableHeadersRow(); - - var tpList = Service.Get(); - - for (var i = 0; i < tpList.Length; i++) - { - var info = tpList[i]; - if (info == null) - continue; - - ImGui.TableNextColumn(); // Idx - ImGui.TextUnformatted($"{i}"); - - ImGui.TableNextColumn(); // Name - ImGui.TextUnformatted($"{info.AetheryteData.GameData.PlaceName.Value?.Name}"); - - ImGui.TableNextColumn(); // ID - ImGui.TextUnformatted($"{info.AetheryteId}"); - - ImGui.TableNextColumn(); // Zone - ImGui.TextUnformatted($"{info.TerritoryId}"); - - ImGui.TableNextColumn(); // Ward - ImGui.TextUnformatted($"{info.Ward}"); - - ImGui.TableNextColumn(); // Plot - ImGui.TextUnformatted($"{info.Plot}"); - - ImGui.TableNextColumn(); // Sub - ImGui.TextUnformatted($"{info.SubIndex}"); - - ImGui.TableNextColumn(); // Gil - ImGui.TextUnformatted($"{info.GilCost}"); - - ImGui.TableNextColumn(); // Favourite - ImGui.TextUnformatted($"{info.IsFavourite}"); - - ImGui.TableNextColumn(); // Shared - ImGui.TextUnformatted($"{info.IsSharedHouse}"); - - ImGui.TableNextColumn(); // Appartment - ImGui.TextUnformatted($"{info.IsAppartment}"); - } - - ImGui.EndTable(); - } - - private void DrawDtr() - { - this.DrawDtrTestEntry(ref this.dtrTest1, "DTR Test #1"); - ImGui.Separator(); - this.DrawDtrTestEntry(ref this.dtrTest2, "DTR Test #2"); - ImGui.Separator(); - this.DrawDtrTestEntry(ref this.dtrTest3, "DTR Test #3"); - ImGui.Separator(); - - var configuration = Service.Get(); - if (configuration.DtrOrder != null) - { - ImGui.Separator(); - - foreach (var order in configuration.DtrOrder) - { - ImGui.Text(order); - } - } - } - - private void DrawDtrTestEntry(ref DtrBarEntry? entry, string title) - { - var dtrBar = Service.Get(); - - if (entry != null) - { - ImGui.Text(title); - - var text = entry.Text?.TextValue ?? string.Empty; - if (ImGui.InputText($"Text###{entry.Title}t", ref text, 255)) - entry.Text = text; - - var shown = entry.Shown; - if (ImGui.Checkbox($"Shown###{entry.Title}s", ref shown)) - entry.Shown = shown; - - if (ImGui.Button($"Remove###{entry.Title}r")) - { - entry.Remove(); - entry = null; + ImGuiHelpers.ScaledDummy(15); + ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:"); + ImGui.TextUnformatted(task.Exception.ToString()); } } else { - if (ImGui.Button($"Add###{title}")) - { - entry = dtrBar.Get(title, title); - } - } - } - - private async Task TestTaskInTaskDelay(CancellationToken token) - { - await Task.Delay(5000, token); - } - -#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"; + task.IsBeingViewed = false; } - if (actor is PlayerCharacter pc) + 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")) { - 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"; + this.messageBoxMinHook?.Dispose(); + this.messageBoxMinHook = null; } - ImGui.TextUnformatted(actorString); - ImGui.SameLine(); - if (ImGui.Button($"C##{this.copyButtonIndex++}")) + 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 void DrawAetherytes() + { + if (!ImGui.BeginTable("##aetheryteTable", 11, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) + return; + + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("Idx", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Zone", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Ward", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Plot", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Sub", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Gil", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Fav", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Shared", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Appartment", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableHeadersRow(); + + var tpList = Service.Get(); + + for (var i = 0; i < tpList.Length; i++) + { + var info = tpList[i]; + if (info == null) + continue; + + ImGui.TableNextColumn(); // Idx + ImGui.TextUnformatted($"{i}"); + + ImGui.TableNextColumn(); // Name + ImGui.TextUnformatted($"{info.AetheryteData.GameData.PlaceName.Value?.Name}"); + + ImGui.TableNextColumn(); // ID + ImGui.TextUnformatted($"{info.AetheryteId}"); + + ImGui.TableNextColumn(); // Zone + ImGui.TextUnformatted($"{info.TerritoryId}"); + + ImGui.TableNextColumn(); // Ward + ImGui.TextUnformatted($"{info.Ward}"); + + ImGui.TableNextColumn(); // Plot + ImGui.TextUnformatted($"{info.Plot}"); + + ImGui.TableNextColumn(); // Sub + ImGui.TextUnformatted($"{info.SubIndex}"); + + ImGui.TableNextColumn(); // Gil + ImGui.TextUnformatted($"{info.GilCost}"); + + ImGui.TableNextColumn(); // Favourite + ImGui.TextUnformatted($"{info.IsFavourite}"); + + ImGui.TableNextColumn(); // Shared + ImGui.TextUnformatted($"{info.IsSharedHouse}"); + + ImGui.TableNextColumn(); // Appartment + ImGui.TextUnformatted($"{info.IsAppartment}"); + } + + ImGui.EndTable(); + } + + private void DrawDtr() + { + this.DrawDtrTestEntry(ref this.dtrTest1, "DTR Test #1"); + ImGui.Separator(); + this.DrawDtrTestEntry(ref this.dtrTest2, "DTR Test #2"); + ImGui.Separator(); + this.DrawDtrTestEntry(ref this.dtrTest3, "DTR Test #3"); + ImGui.Separator(); + + var configuration = Service.Get(); + if (configuration.DtrOrder != null) + { + ImGui.Separator(); + + foreach (var order in configuration.DtrOrder) { - ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X")); + ImGui.Text(order); } } } + + private void DrawDtrTestEntry(ref DtrBarEntry? entry, string title) + { + var dtrBar = Service.Get(); + + if (entry != null) + { + ImGui.Text(title); + + var text = entry.Text?.TextValue ?? string.Empty; + if (ImGui.InputText($"Text###{entry.Title}t", ref text, 255)) + entry.Text = text; + + var shown = entry.Shown; + if (ImGui.Checkbox($"Shown###{entry.Title}s", ref shown)) + entry.Shown = shown; + + if (ImGui.Button($"Remove###{entry.Title}r")) + { + entry.Remove(); + entry = null; + } + } + else + { + if (ImGui.Button($"Add###{title}")) + { + entry = dtrBar.Get(title, title); + } + } + } + + private async Task TestTaskInTaskDelay(CancellationToken token) + { + await Task.Delay(5000, token); + } + +#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 d9e508357..80e03caf3 100644 --- a/Dalamud/Interface/Internal/Windows/IMEWindow.cs +++ b/Dalamud/Interface/Internal/Windows/IMEWindow.cs @@ -5,117 +5,116 @@ using Dalamud.Game.Gui.Internal; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// A window for displaying IME details. +/// +internal unsafe class ImeWindow : Window { + private const int ImePageSize = 9; + /// - /// A window for displaying IME details. + /// Initializes a new instance of the class. /// - internal unsafe class ImeWindow : Window + public ImeWindow() + : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground) { - 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 | ImGuiWindowFlags.NoBackground) + this.RespectCloseHotkey = false; + } + + /// + public override void Draw() + { + if (this.IsOpen && Service.Get()[VirtualKey.SHIFT]) Service.Get().CloseImeWindow(); + 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.GetCursorPos()}"); + // ImGui.Text($"{ImGui.GetWindowViewport().WorkSize}"); + } + + /// + public override void PostDraw() + { + if (this.IsOpen && Service.Get()[VirtualKey.SHIFT]) Service.Get().CloseImeWindow(); + var ime = Service.GetNullable(); + + if (ime == null || !ime.IsEnabled) + return; + + var maxTextWidth = 0f; + var textHeight = ImGui.CalcTextSize(ime.ImmComp).Y; + + var native = ime.ImmCandNative; + var totalIndex = native.Selection + 1; + var totalSize = native.Count; + + var pageStart = native.PageStart; + var pageIndex = (pageStart / ImePageSize) + 1; + var pageCount = (totalSize / ImePageSize) + 1; + var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"; + + // Calc the window size + for (var i = 0; i < ime.ImmCand.Count; i++) { - if (this.IsOpen && Service.Get()[VirtualKey.SHIFT]) Service.Get().CloseImeWindow(); - var ime = Service.GetNullable(); - - if (ime == null || !ime.IsEnabled) - { - ImGui.Text("IME is unavailable."); - return; - } - - // ImGui.Text($"{ime.GetCursorPos()}"); - // ImGui.Text($"{ImGui.GetWindowViewport().WorkSize}"); + var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}"); + maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X; } - /// - public override void PostDraw() + maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X; + maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X ? maxTextWidth : ImGui.CalcTextSize(ime.ImmComp).X; + + var imeWindowWidth = maxTextWidth + (2 * ImGui.GetStyle().WindowPadding.X); + var imeWindowHeight = (textHeight * (ime.ImmCand.Count + 2)) + (5 * (ime.ImmCand.Count - 1)) + (2 * ImGui.GetStyle().WindowPadding.Y); + + // Calc the window pos + var cursorPos = ime.GetCursorPos(); + var imeWindowMinPos = new Vector2(cursorPos.X, cursorPos.Y); + var imeWindowMaxPos = new Vector2(imeWindowMinPos.X + imeWindowWidth, imeWindowMinPos.Y + imeWindowHeight); + var gameWindowSize = ImGui.GetWindowViewport().WorkSize; + + var offset = new Vector2( + imeWindowMaxPos.X - gameWindowSize.X > 0 ? imeWindowMaxPos.X - gameWindowSize.X : 0, + imeWindowMaxPos.Y - gameWindowSize.Y > 0 ? imeWindowMaxPos.Y - gameWindowSize.Y : 0); + imeWindowMinPos -= offset; + imeWindowMaxPos -= offset; + + var nextDrawPosY = imeWindowMinPos.Y; + var drawAreaPosX = imeWindowMinPos.X + ImGui.GetStyle().WindowPadding.X; + + // Draw the ime window + var drawList = ImGui.GetForegroundDrawList(); + // Draw the background rect + drawList.AddRectFilled(imeWindowMinPos, imeWindowMaxPos, ImGui.GetColorU32(ImGuiCol.WindowBg), ImGui.GetStyle().WindowRounding); + // Add component text + drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp); + nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y; + // Add separator + drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator)); + // Add candidate words + for (var i = 0; i < ime.ImmCand.Count; i++) { - if (this.IsOpen && Service.Get()[VirtualKey.SHIFT]) Service.Get().CloseImeWindow(); - var ime = Service.GetNullable(); + var selected = i == (native.Selection % ImePageSize); + var color = ImGui.GetColorU32(ImGuiCol.Text); + if (selected) + color = ImGui.GetColorU32(ImGuiCol.NavHighlight); - if (ime == null || !ime.IsEnabled) - return; - - var maxTextWidth = 0f; - var textHeight = ImGui.CalcTextSize(ime.ImmComp).Y; - - var native = ime.ImmCandNative; - var totalIndex = native.Selection + 1; - var totalSize = native.Count; - - var pageStart = native.PageStart; - var pageIndex = (pageStart / ImePageSize) + 1; - var pageCount = (totalSize / ImePageSize) + 1; - var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"; - - // Calc the window size - for (var i = 0; i < ime.ImmCand.Count; i++) - { - var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}"); - maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X; - } - - maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X; - maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X ? maxTextWidth : ImGui.CalcTextSize(ime.ImmComp).X; - - var imeWindowWidth = maxTextWidth + (2 * ImGui.GetStyle().WindowPadding.X); - var imeWindowHeight = (textHeight * (ime.ImmCand.Count + 2)) + (5 * (ime.ImmCand.Count - 1)) + (2 * ImGui.GetStyle().WindowPadding.Y); - - // Calc the window pos - var cursorPos = ime.GetCursorPos(); - var imeWindowMinPos = new Vector2(cursorPos.X, cursorPos.Y); - var imeWindowMaxPos = new Vector2(imeWindowMinPos.X + imeWindowWidth, imeWindowMinPos.Y + imeWindowHeight); - var gameWindowSize = ImGui.GetWindowViewport().WorkSize; - - var offset = new Vector2( - imeWindowMaxPos.X - gameWindowSize.X > 0 ? imeWindowMaxPos.X - gameWindowSize.X : 0, - imeWindowMaxPos.Y - gameWindowSize.Y > 0 ? imeWindowMaxPos.Y - gameWindowSize.Y : 0); - imeWindowMinPos -= offset; - imeWindowMaxPos -= offset; - - var nextDrawPosY = imeWindowMinPos.Y; - var drawAreaPosX = imeWindowMinPos.X + ImGui.GetStyle().WindowPadding.X; - - // Draw the ime window - var drawList = ImGui.GetForegroundDrawList(); - // Draw the background rect - drawList.AddRectFilled(imeWindowMinPos, imeWindowMaxPos, ImGui.GetColorU32(ImGuiCol.WindowBg), ImGui.GetStyle().WindowRounding); - // Add component text - drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp); + drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), color, $"{i + 1}. {ime.ImmCand[i]}"); nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y; - // Add separator - drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator)); - // Add candidate words - for (var i = 0; i < ime.ImmCand.Count; i++) - { - var selected = i == (native.Selection % ImePageSize); - var color = ImGui.GetColorU32(ImGuiCol.Text); - if (selected) - color = ImGui.GetColorU32(ImGuiCol.NavHighlight); - - drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), color, $"{i + 1}. {ime.ImmCand[i]}"); - nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y; - } - - // Add separator - drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator)); - // Add pages infomation - drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), pageInfo); } + + // Add separator + drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator)); + // Add pages infomation + drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), pageInfo); } } diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 8f7e61e1f..d9e15abea 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -14,445 +14,402 @@ using Dalamud.Utility; using ImGuiScene; using Serilog; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// A cache for plugin icons and images. +/// +[ServiceManager.EarlyLoadedService] +internal class PluginImageCache : IDisposable, IServiceType { /// - /// A cache for plugin icons and images. + /// Maximum plugin image width. /// - [ServiceManager.EarlyLoadedService] - internal class PluginImageCache : IDisposable, IServiceType + 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; + + private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}"; + private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}"; + + private readonly BlockingCollection>> downloadQueue = new(); + private readonly BlockingCollection> loadQueue = new(); + private readonly CancellationTokenSource cancelToken = new(); + private readonly Task downloadTask; + private readonly Task loadTask; + + private readonly ConcurrentDictionary pluginIconMap = new(); + private readonly ConcurrentDictionary pluginImagesMap = new(); + + private readonly Task emptyTextureTask; + private readonly Task disabledIconTask; + private readonly Task outdatedInstallableIconTask; + private readonly Task defaultIconTask; + private readonly Task troubleIconTask; + private readonly Task updateIconTask; + private readonly Task installedIconTask; + private readonly Task thirdIconTask; + private readonly Task thirdInstalledIconTask; + private readonly Task corePluginIconTask; + + [ServiceManager.ServiceConstructor] + private PluginImageCache(Dalamud dalamud) { - /// - /// Maximum plugin image width. - /// - public const int PluginImageWidth = 730; + var imwst = Service.GetAsync(); - /// - /// Maximum plugin image height. - /// - public const int PluginImageHeight = 380; + Task? TaskWrapIfNonNull(TextureWrap? tw) => tw == null ? null : Task.FromResult(tw!); - /// - /// Maximum plugin icon width. - /// - public const int PluginIconWidth = 512; + this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!); + this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.disabledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "disabledIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.outdatedInstallableIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "outdatedInstallableIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.thirdIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.thirdInstalledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdInstalledIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.corePluginIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png"))) ?? this.emptyTextureTask).Unwrap(); - /// - /// Maximum plugin height. - /// - public const int PluginIconHeight = 512; + this.downloadTask = Task.Factory.StartNew( + () => this.DownloadTask(8), TaskCreationOptions.LongRunning); + this.loadTask = Task.Factory.StartNew( + () => this.LoadTask(Environment.ProcessorCount), TaskCreationOptions.LongRunning); + } - private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}"; - private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}"; + /// + /// Gets the fallback empty texture. + /// + public TextureWrap EmptyTexture => this.emptyTextureTask.IsCompleted + ? this.emptyTextureTask.Result + : this.emptyTextureTask.GetAwaiter().GetResult(); - private readonly BlockingCollection>> downloadQueue = new(); - private readonly BlockingCollection> loadQueue = new(); - private readonly CancellationTokenSource cancelToken = new(); - private readonly Task downloadTask; - private readonly Task loadTask; + /// + /// Gets the disabled plugin icon. + /// + public TextureWrap DisabledIcon => this.disabledIconTask.IsCompleted + ? this.disabledIconTask.Result + : this.disabledIconTask.GetAwaiter().GetResult(); - private readonly ConcurrentDictionary pluginIconMap = new(); - private readonly ConcurrentDictionary pluginImagesMap = new(); + /// + /// Gets the outdated installable plugin icon. + /// + public TextureWrap OutdatedInstallableIcon => this.outdatedInstallableIconTask.IsCompleted + ? this.outdatedInstallableIconTask.Result + : this.outdatedInstallableIconTask.GetAwaiter().GetResult(); - private readonly Task emptyTextureTask; - private readonly Task disabledIconTask; - private readonly Task outdatedInstallableIconTask; - private readonly Task defaultIconTask; - private readonly Task troubleIconTask; - private readonly Task updateIconTask; - private readonly Task installedIconTask; - private readonly Task thirdIconTask; - private readonly Task thirdInstalledIconTask; - private readonly Task corePluginIconTask; + /// + /// Gets the default plugin icon. + /// + public TextureWrap DefaultIcon => this.defaultIconTask.IsCompleted + ? this.defaultIconTask.Result + : this.defaultIconTask.GetAwaiter().GetResult(); - [ServiceManager.ServiceConstructor] - private PluginImageCache(Dalamud dalamud) + /// + /// Gets the plugin trouble icon overlay. + /// + public TextureWrap TroubleIcon => this.troubleIconTask.IsCompleted + ? this.troubleIconTask.Result + : this.troubleIconTask.GetAwaiter().GetResult(); + + /// + /// Gets the plugin update icon overlay. + /// + public TextureWrap UpdateIcon => this.updateIconTask.IsCompleted + ? this.updateIconTask.Result + : this.updateIconTask.GetAwaiter().GetResult(); + + /// + /// Gets the plugin installed icon overlay. + /// + public TextureWrap InstalledIcon => this.installedIconTask.IsCompleted + ? this.installedIconTask.Result + : this.installedIconTask.GetAwaiter().GetResult(); + + /// + /// Gets the third party plugin icon overlay. + /// + public TextureWrap ThirdIcon => this.thirdIconTask.IsCompleted + ? this.thirdIconTask.Result + : this.thirdIconTask.GetAwaiter().GetResult(); + + /// + /// Gets the installed third party plugin icon overlay. + /// + public TextureWrap ThirdInstalledIcon => this.thirdInstalledIconTask.IsCompleted + ? this.thirdInstalledIconTask.Result + : this.thirdInstalledIconTask.GetAwaiter().GetResult(); + + /// + /// Gets the core plugin icon. + /// + public TextureWrap CorePluginIcon => this.corePluginIconTask.IsCompleted + ? this.corePluginIconTask.Result + : this.corePluginIconTask.GetAwaiter().GetResult(); + + /// + public void Dispose() + { + this.cancelToken.Cancel(); + this.downloadQueue.CompleteAdding(); + this.loadQueue.CompleteAdding(); + + if (!Task.WaitAll(new[] { this.loadTask, this.downloadTask }, 4000)) { - var imwst = Service.GetAsync(); - - Task? TaskWrapIfNonNull(TextureWrap? tw) => tw == null ? null : Task.FromResult(tw!); - - this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!); - this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.disabledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "disabledIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.outdatedInstallableIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "outdatedInstallableIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.thirdIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.thirdInstalledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdInstalledIcon.png"))) ?? this.emptyTextureTask).Unwrap(); - this.corePluginIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png"))) ?? this.emptyTextureTask).Unwrap(); - - this.downloadTask = Task.Factory.StartNew( - () => this.DownloadTask(8), TaskCreationOptions.LongRunning); - this.loadTask = Task.Factory.StartNew( - () => this.LoadTask(Environment.ProcessorCount), TaskCreationOptions.LongRunning); + Log.Error("Plugin Image download/load thread has not cancelled in time"); } - /// - /// Gets the fallback empty texture. - /// - public TextureWrap EmptyTexture => this.emptyTextureTask.IsCompleted - ? this.emptyTextureTask.Result - : this.emptyTextureTask.GetAwaiter().GetResult(); + this.cancelToken.Dispose(); + this.downloadQueue.Dispose(); + this.loadQueue.Dispose(); - /// - /// Gets the disabled plugin icon. - /// - public TextureWrap DisabledIcon => this.disabledIconTask.IsCompleted - ? this.disabledIconTask.Result - : this.disabledIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the outdated installable plugin icon. - /// - public TextureWrap OutdatedInstallableIcon => this.outdatedInstallableIconTask.IsCompleted - ? this.outdatedInstallableIconTask.Result - : this.outdatedInstallableIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the default plugin icon. - /// - public TextureWrap DefaultIcon => this.defaultIconTask.IsCompleted - ? this.defaultIconTask.Result - : this.defaultIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the plugin trouble icon overlay. - /// - public TextureWrap TroubleIcon => this.troubleIconTask.IsCompleted - ? this.troubleIconTask.Result - : this.troubleIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the plugin update icon overlay. - /// - public TextureWrap UpdateIcon => this.updateIconTask.IsCompleted - ? this.updateIconTask.Result - : this.updateIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the plugin installed icon overlay. - /// - public TextureWrap InstalledIcon => this.installedIconTask.IsCompleted - ? this.installedIconTask.Result - : this.installedIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the third party plugin icon overlay. - /// - public TextureWrap ThirdIcon => this.thirdIconTask.IsCompleted - ? this.thirdIconTask.Result - : this.thirdIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the installed third party plugin icon overlay. - /// - public TextureWrap ThirdInstalledIcon => this.thirdInstalledIconTask.IsCompleted - ? this.thirdInstalledIconTask.Result - : this.thirdInstalledIconTask.GetAwaiter().GetResult(); - - /// - /// Gets the core plugin icon. - /// - public TextureWrap CorePluginIcon => this.corePluginIconTask.IsCompleted - ? this.corePluginIconTask.Result - : this.corePluginIconTask.GetAwaiter().GetResult(); - - /// - public void Dispose() + foreach (var task in new[] + { + this.defaultIconTask, + this.troubleIconTask, + this.updateIconTask, + this.installedIconTask, + this.thirdIconTask, + this.thirdInstalledIconTask, + this.corePluginIconTask, + }) { - this.cancelToken.Cancel(); - this.downloadQueue.CompleteAdding(); - this.loadQueue.CompleteAdding(); - - if (!Task.WaitAll(new[] { this.loadTask, this.downloadTask }, 4000)) - { - Log.Error("Plugin Image download/load thread has not cancelled in time"); - } - - this.cancelToken.Dispose(); - this.downloadQueue.Dispose(); - this.loadQueue.Dispose(); - - foreach (var task in new[] - { - this.defaultIconTask, - this.troubleIconTask, - this.updateIconTask, - this.installedIconTask, - this.thirdIconTask, - this.thirdInstalledIconTask, - this.corePluginIconTask, - }) - { - task.Wait(); - if (task.IsCompletedSuccessfully) - task.Result.Dispose(); - } - - foreach (var icon in this.pluginIconMap.Values) - { - icon?.Dispose(); - } - - foreach (var images in this.pluginImagesMap.Values) - { - foreach (var image in images) - { - image?.Dispose(); - } - } - - this.pluginIconMap.Clear(); - this.pluginImagesMap.Clear(); + task.Wait(); + if (task.IsCompletedSuccessfully) + task.Result.Dispose(); } - /// - /// Clear the cache of downloaded icons. - /// - public void ClearIconCache() + foreach (var icon in this.pluginIconMap.Values) { - this.pluginIconMap.Clear(); - this.pluginImagesMap.Clear(); + icon?.Dispose(); } - /// - /// 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) + foreach (var images in this.pluginImagesMap.Values) { - if (!this.pluginIconMap.TryAdd(manifest.InternalName, null)) + foreach (var image in images) { - iconTexture = this.pluginIconMap[manifest.InternalName]; - return true; + image?.Dispose(); } - - iconTexture = null; - var requestedFrame = Service.GetNullable()?.FrameCount ?? 0; - Task.Run(async () => - { - try - { - this.pluginIconMap[manifest.InternalName] = - await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty, requestedFrame); - } - catch (Exception ex) - { - Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}"); - } - }); - - 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) + 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.TryAdd(manifest.InternalName, null)) { - if (!this.pluginImagesMap.TryAdd(manifest.InternalName, null)) - { - var found = this.pluginImagesMap[manifest.InternalName]; - imageTextures = found ?? Array.Empty(); - return true; - } - - var target = new TextureWrap?[5]; - this.pluginImagesMap[manifest.InternalName] = target; - imageTextures = target; - - var requestedFrame = Service.GetNullable()?.FrameCount ?? 0; - Task.Run(async () => - { - try - { - await this.DownloadPluginImagesAsync(target, plugin, manifest, isThirdParty, requestedFrame); - } - catch (Exception ex) - { - Log.Error(ex, $"An unexpected error occurred with the images for {manifest.InternalName}"); - } - }); - - return false; + iconTexture = this.pluginIconMap[manifest.InternalName]; + return true; } - private static async Task TryLoadImage( - byte[]? bytes, - string name, - string? loc, - PluginManifest manifest, - int maxWidth, - int maxHeight, - bool requireSquare) + iconTexture = null; + var requestedFrame = Service.GetNullable()?.FrameCount ?? 0; + Task.Run(async () => { - if (bytes == null) - return null; - - var interfaceManager = (await Service.GetAsync()).Manager; - var framework = await Service.GetAsync(); - - TextureWrap? image; - // FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread. try { - image = interfaceManager.LoadImage(bytes); + this.pluginIconMap[manifest.InternalName] = + await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty, requestedFrame); } catch (Exception ex) { - Log.Error(ex, "Access violation during load plugin {name} from {Loc} (Async Thread)", name, loc); - - try - { - image = await framework.RunOnFrameworkThread(() => interfaceManager.LoadImage(bytes)); - } - catch (Exception ex2) - { - Log.Error(ex2, "Access violation during load plugin {name} from {Loc} (Framework Thread)", name, loc); - return null; - } + Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}"); } + }); - if (image == null) + 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.TryAdd(manifest.InternalName, null)) + { + var found = this.pluginImagesMap[manifest.InternalName]; + imageTextures = found ?? Array.Empty(); + return true; + } + + var target = new TextureWrap?[5]; + this.pluginImagesMap[manifest.InternalName] = target; + imageTextures = target; + + var requestedFrame = Service.GetNullable()?.FrameCount ?? 0; + Task.Run(async () => + { + try { - Log.Error($"Could not load {name} for {manifest.InternalName} at {loc}"); + await this.DownloadPluginImagesAsync(target, plugin, manifest, isThirdParty, requestedFrame); + } + catch (Exception ex) + { + Log.Error(ex, $"An unexpected error occurred with the images for {manifest.InternalName}"); + } + }); + + return false; + } + + private static async Task TryLoadImage( + byte[]? bytes, + string name, + string? loc, + PluginManifest manifest, + int maxWidth, + int maxHeight, + bool requireSquare) + { + if (bytes == null) + return null; + + var interfaceManager = (await Service.GetAsync()).Manager; + var framework = await Service.GetAsync(); + + TextureWrap? image; + // FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread. + try + { + image = interfaceManager.LoadImage(bytes); + } + catch (Exception ex) + { + Log.Error(ex, "Access violation during load plugin {name} from {Loc} (Async Thread)", name, loc); + + try + { + image = await framework.RunOnFrameworkThread(() => interfaceManager.LoadImage(bytes)); + } + catch (Exception ex2) + { + Log.Error(ex2, "Access violation during load plugin {name} from {Loc} (Framework Thread)", name, loc); return null; } + } - if (image.Width > maxWidth || image.Height > maxHeight) + if (image == null) + { + Log.Error($"Could not load {name} for {manifest.InternalName} at {loc}"); + return null; + } + + if (image.Width > maxWidth || image.Height > maxHeight) + { + Log.Error($"Plugin {name} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({image.Width}x{image.Height} > {maxWidth}x{maxHeight})."); + image.Dispose(); + return null; + } + + if (requireSquare && image.Height != image.Width) + { + Log.Error($"Plugin {name} for {manifest.InternalName} at {loc} was not square."); + image.Dispose(); + return null; + } + + return image!; + } + + private Task RunInDownloadQueue(Func> func, ulong requestedFrame) + { + var tcs = new TaskCompletionSource(); + this.downloadQueue.Add(Tuple.Create(requestedFrame, async () => + { + try { - Log.Error($"Plugin {name} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({image.Width}x{image.Height} > {maxWidth}x{maxHeight})."); - image.Dispose(); - return null; + tcs.SetResult(await func()); } - - if (requireSquare && image.Height != image.Width) + catch (Exception e) { - Log.Error($"Plugin {name} for {manifest.InternalName} at {loc} was not square."); - image.Dispose(); - return null; + tcs.SetException(e); } + })); + return tcs.Task; + } - return image!; - } - - private Task RunInDownloadQueue(Func> func, ulong requestedFrame) + private Task RunInLoadQueue(Func> func) + { + var tcs = new TaskCompletionSource(); + this.loadQueue.Add(async () => { - var tcs = new TaskCompletionSource(); - this.downloadQueue.Add(Tuple.Create(requestedFrame, async () => + try { - try - { - tcs.SetResult(await func()); - } - catch (Exception e) - { - tcs.SetException(e); - } - })); - return tcs.Task; - } - - private Task RunInLoadQueue(Func> func) - { - var tcs = new TaskCompletionSource(); - this.loadQueue.Add(async () => - { - try - { - tcs.SetResult(await func()); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - return tcs.Task; - } - - private async Task DownloadTask(int concurrency) - { - var token = this.cancelToken.Token; - var runningTasks = new List(); - var pendingFuncs = new List>>(); - while (true) - { - try - { - token.ThrowIfCancellationRequested(); - if (!pendingFuncs.Any()) - { - if (!this.downloadQueue.TryTake(out var taskTuple, -1, token)) - return; - - pendingFuncs.Add(taskTuple); - } - - token.ThrowIfCancellationRequested(); - while (this.downloadQueue.TryTake(out var taskTuple, 0, token)) - pendingFuncs.Add(taskTuple); - - // Process most recently requested items first in terms of frame index. - pendingFuncs = pendingFuncs.OrderBy(x => x.Item1).ToList(); - - var item1 = pendingFuncs.Last().Item1; - while (pendingFuncs.Any() && pendingFuncs.Last().Item1 == item1) - { - token.ThrowIfCancellationRequested(); - while (runningTasks.Count >= concurrency) - { - await Task.WhenAny(runningTasks); - runningTasks.RemoveAll(task => task.IsCompleted); - } - - token.ThrowIfCancellationRequested(); - runningTasks.Add(Task.Run(pendingFuncs.Last().Item2, token)); - pendingFuncs.RemoveAt(pendingFuncs.Count - 1); - } - } - catch (OperationCanceledException) - { - // Shutdown signal. - break; - } - catch (Exception ex) - { - Log.Error(ex, "An unhandled exception occurred in the plugin image downloader"); - } - - while (runningTasks.Count >= concurrency) - { - await Task.WhenAny(runningTasks); - runningTasks.RemoveAll(task => task.IsCompleted); - } + tcs.SetResult(await func()); } - - await Task.WhenAll(runningTasks); - Log.Debug("Plugin image downloader has shutdown"); - } - - private async Task LoadTask(int concurrency) - { - await Service.GetAsync(); - - var token = this.cancelToken.Token; - var runningTasks = new List(); - while (true) + catch (Exception e) { - try + tcs.SetException(e); + } + }); + return tcs.Task; + } + + private async Task DownloadTask(int concurrency) + { + var token = this.cancelToken.Token; + var runningTasks = new List(); + var pendingFuncs = new List>>(); + while (true) + { + try + { + token.ThrowIfCancellationRequested(); + if (!pendingFuncs.Any()) + { + if (!this.downloadQueue.TryTake(out var taskTuple, -1, token)) + return; + + pendingFuncs.Add(taskTuple); + } + + token.ThrowIfCancellationRequested(); + while (this.downloadQueue.TryTake(out var taskTuple, 0, token)) + pendingFuncs.Add(taskTuple); + + // Process most recently requested items first in terms of frame index. + pendingFuncs = pendingFuncs.OrderBy(x => x.Item1).ToList(); + + var item1 = pendingFuncs.Last().Item1; + while (pendingFuncs.Any() && pendingFuncs.Last().Item1 == item1) { token.ThrowIfCancellationRequested(); while (runningTasks.Count >= concurrency) @@ -461,38 +418,81 @@ namespace Dalamud.Interface.Internal.Windows runningTasks.RemoveAll(task => task.IsCompleted); } - if (!this.loadQueue.TryTake(out var func, -1, token)) - return; - runningTasks.Add(Task.Run(func, token)); - } - catch (OperationCanceledException) - { - // Shutdown signal. - break; - } - catch (Exception ex) - { - Log.Error(ex, "An unhandled exception occurred in the plugin image loader"); + token.ThrowIfCancellationRequested(); + runningTasks.Add(Task.Run(pendingFuncs.Last().Item2, token)); + pendingFuncs.RemoveAt(pendingFuncs.Count - 1); } } + catch (OperationCanceledException) + { + // Shutdown signal. + break; + } + catch (Exception ex) + { + Log.Error(ex, "An unhandled exception occurred in the plugin image downloader"); + } - await Task.WhenAll(runningTasks); - Log.Debug("Plugin image loader has shutdown"); + while (runningTasks.Count >= concurrency) + { + await Task.WhenAny(runningTasks); + runningTasks.RemoveAll(task => task.IsCompleted); + } } - private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, ulong requestedFrame) - { - if (plugin is { IsDev: true }) - { - var file = this.GetPluginIconFileInfo(plugin); - if (file != null) - { - Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}"); + await Task.WhenAll(runningTasks); + Log.Debug("Plugin image downloader has shutdown"); + } - var fileBytes = await this.RunInDownloadQueue( + private async Task LoadTask(int concurrency) + { + await Service.GetAsync(); + + var token = this.cancelToken.Token; + var runningTasks = new List(); + while (true) + { + try + { + token.ThrowIfCancellationRequested(); + while (runningTasks.Count >= concurrency) + { + await Task.WhenAny(runningTasks); + runningTasks.RemoveAll(task => task.IsCompleted); + } + + if (!this.loadQueue.TryTake(out var func, -1, token)) + return; + runningTasks.Add(Task.Run(func, token)); + } + catch (OperationCanceledException) + { + // Shutdown signal. + break; + } + catch (Exception ex) + { + Log.Error(ex, "An unhandled exception occurred in the plugin image loader"); + } + } + + await Task.WhenAll(runningTasks); + Log.Debug("Plugin image loader has shutdown"); + } + + private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, ulong requestedFrame) + { + if (plugin is { IsDev: true }) + { + var file = this.GetPluginIconFileInfo(plugin); + if (file != null) + { + Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}"); + + var fileBytes = await this.RunInDownloadQueue( () => File.ReadAllBytesAsync(file.FullName), requestedFrame); - var fileIcon = await this.RunInLoadQueue( + var fileIcon = await this.RunInLoadQueue( () => TryLoadImage( fileBytes, "icon", @@ -501,237 +501,236 @@ namespace Dalamud.Interface.Internal.Windows PluginIconWidth, PluginIconHeight, true)); - if (fileIcon != null) - { - Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk"); - return fileIcon; - } + if (fileIcon != null) + { + Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk"); + return fileIcon; } - - // 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 = Service.Get().UseTesting(manifest); - var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting); - - if (url.IsNullOrEmpty()) - { - Log.Verbose($"Plugin icon for {manifest.InternalName} is not available"); - return null; - } - - Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}"); - - // ReSharper disable once RedundantTypeArgumentsOfMethod - var bytes = await this.RunInDownloadQueue( - async () => - { - var data = await Util.HttpClient.GetAsync(url); - if (data.StatusCode == HttpStatusCode.NotFound) - return null; - - data.EnsureSuccessStatusCode(); - return await data.Content.ReadAsByteArrayAsync(); - }, - requestedFrame); - - if (bytes == null) - return null; - - var icon = await this.RunInLoadQueue( - () => TryLoadImage(bytes, "icon", url, manifest, PluginIconWidth, PluginIconHeight, true)); - if (icon != null) - Log.Verbose($"Plugin icon for {manifest.InternalName} loaded"); - return icon; + // 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(TextureWrap?[] pluginImages, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, ulong requestedFrame) + var useTesting = Service.Get().UseTesting(manifest); + var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting); + + if (url.IsNullOrEmpty()) { - if (plugin is { IsDev: true }) - { - var fileTasks = new List(); - var files = this.GetPluginImageFileInfos(plugin) - .Where(x => x is { Exists: true }) - .Select(x => (FileInfo)x!) - .ToList(); - for (var i = 0; i < files.Count && i < pluginImages.Length; i++) - { - var file = files[i]; - var i2 = i; - fileTasks.Add(Task.Run(async () => - { - var bytes = await this.RunInDownloadQueue( - () => File.ReadAllBytesAsync(file.FullName), - requestedFrame); - var image = await this.RunInLoadQueue( - () => TryLoadImage( - bytes, - $"image{i2 + 1}", - file.FullName, - manifest, - PluginImageWidth, - PluginImageHeight, - false)); - if (image == null) - return; - - Log.Verbose($"Plugin image{i2 + 1} for {manifest.InternalName} loaded from disk"); - pluginImages[i2] = image; - })); - } - - try - { - await Task.WhenAll(fileTasks); - } - catch (Exception ex) - { - Log.Error(ex, $"Failed to load at least one plugin image from filesystem"); - } - - if (pluginImages.Any(x => x != null)) - 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 = Service.Get().UseTesting(manifest); - var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting); - urls = urls?.Where(x => !string.IsNullOrEmpty(x)).ToList(); - if (urls?.Any() != true) - { - Log.Verbose($"Images for {manifest.InternalName} are not available"); - return; - } - - var tasks = new List(); - for (var i = 0; i < urls.Count && i < pluginImages.Length; i++) + Log.Verbose($"Plugin icon for {manifest.InternalName} is not available"); + return null; + } + + Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}"); + + // ReSharper disable once RedundantTypeArgumentsOfMethod + var bytes = await this.RunInDownloadQueue( + async () => + { + var data = await Util.HttpClient.GetAsync(url); + if (data.StatusCode == HttpStatusCode.NotFound) + return null; + + data.EnsureSuccessStatusCode(); + return await data.Content.ReadAsByteArrayAsync(); + }, + requestedFrame); + + if (bytes == null) + return null; + + var icon = await this.RunInLoadQueue( + () => TryLoadImage(bytes, "icon", url, manifest, PluginIconWidth, PluginIconHeight, true)); + if (icon != null) + Log.Verbose($"Plugin icon for {manifest.InternalName} loaded"); + return icon; + } + + private async Task DownloadPluginImagesAsync(TextureWrap?[] pluginImages, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, ulong requestedFrame) + { + if (plugin is { IsDev: true }) + { + var fileTasks = new List(); + var files = this.GetPluginImageFileInfos(plugin) + .Where(x => x is { Exists: true }) + .Select(x => (FileInfo)x!) + .ToList(); + for (var i = 0; i < files.Count && i < pluginImages.Length; i++) { + var file = files[i]; var i2 = i; - var url = urls[i]; - tasks.Add(Task.Run(async () => + fileTasks.Add(Task.Run(async () => { - Log.Verbose($"Downloading image{i2 + 1} for {manifest.InternalName} from {url}"); - // ReSharper disable once RedundantTypeArgumentsOfMethod - var bytes = await this.RunInDownloadQueue( - async () => - { - var data = await Util.HttpClient.GetAsync(url); - if (data.StatusCode == HttpStatusCode.NotFound) - return null; - - data.EnsureSuccessStatusCode(); - return await data.Content.ReadAsByteArrayAsync(); - }, + var bytes = await this.RunInDownloadQueue( + () => File.ReadAllBytesAsync(file.FullName), requestedFrame); - - if (bytes == null) - return; - - var image = await TryLoadImage( + var image = await this.RunInLoadQueue( + () => TryLoadImage( bytes, $"image{i2 + 1}", - "queue", + file.FullName, manifest, PluginImageWidth, PluginImageHeight, - false); + false)); if (image == null) return; - Log.Verbose($"Image{i2 + 1} for {manifest.InternalName} loaded"); + Log.Verbose($"Plugin image{i2 + 1} for {manifest.InternalName} loaded from disk"); pluginImages[i2] = image; })); } try { - await Task.WhenAll(tasks); + await Task.WhenAll(fileTasks); } catch (Exception ex) { - Log.Error(ex, "Failed to load at least one plugin image from network."); + Log.Error(ex, $"Failed to load at least one plugin image from filesystem"); } + + if (pluginImages.Any(x => x != null)) + 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; } - private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting) + var useTesting = Service.Get().UseTesting(manifest); + var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting); + urls = urls?.Where(x => !string.IsNullOrEmpty(x)).ToList(); + if (urls?.Any() != true) { - if (isThirdParty) - return manifest.IconUrl; - - if (manifest.IsDip17Plugin) - return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png"); - - return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png"); + Log.Verbose($"Images for {manifest.InternalName} are not available"); + return; } - private List? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting) + var tasks = new List(); + for (var i = 0; i < urls.Count && i < pluginImages.Length; i++) { - if (isThirdParty) + var i2 = i; + var url = urls[i]; + tasks.Add(Task.Run(async () => { - if (manifest.ImageUrls?.Count > 5) - { - Log.Warning($"Plugin {manifest.InternalName} has too many images"); - return manifest.ImageUrls.Take(5).ToList(); - } + Log.Verbose($"Downloading image{i2 + 1} for {manifest.InternalName} from {url}"); + // ReSharper disable once RedundantTypeArgumentsOfMethod + var bytes = await this.RunInDownloadQueue( + async () => + { + var data = await Util.HttpClient.GetAsync(url); + if (data.StatusCode == HttpStatusCode.NotFound) + return null; - return manifest.ImageUrls; - } + data.EnsureSuccessStatusCode(); + return await data.Content.ReadAsByteArrayAsync(); + }, + requestedFrame); - var output = new List(); - for (var i = 1; i <= 5; i++) - { - if (manifest.IsDip17Plugin) - { - output.Add(MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, $"image{i}.png")); - } - else - { - output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); - } - } + if (bytes == null) + return; - return output; + var image = await TryLoadImage( + bytes, + $"image{i2 + 1}", + "queue", + manifest, + PluginImageWidth, + PluginImageHeight, + false); + if (image == null) + return; + + Log.Verbose($"Image{i2 + 1} for {manifest.InternalName} loaded"); + pluginImages[i2] = image; + })); } - private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin) + try { - var pluginDir = plugin?.DllFile.Directory; - if (pluginDir == null) - return null; - - var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png")); - if (devUrl.Exists) - return devUrl; - - return null; + await Task.WhenAll(tasks); } - - private List GetPluginImageFileInfos(LocalPlugin? plugin) + catch (Exception ex) { - var output = new List(); - - var pluginDir = plugin?.DllFile.Directory; - if (pluginDir == null) - return output; - - 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; + Log.Error(ex, "Failed to load at least one plugin image from network."); } } + + private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting) + { + if (isThirdParty) + return manifest.IconUrl; + + if (manifest.IsDip17Plugin) + return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png"); + + 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++) + { + if (manifest.IsDip17Plugin) + { + output.Add(MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, $"image{i}.png")); + } + else + { + output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); + } + } + + return output; + } + + private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin) + { + var pluginDir = plugin?.DllFile.Directory; + if (pluginDir == null) + return null; + + var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png")); + if (devUrl.Exists) + return devUrl; + + return null; + } + + private List GetPluginImageFileInfos(LocalPlugin? plugin) + { + var output = new List(); + + var pluginDir = plugin?.DllFile.Directory; + if (pluginDir == null) + return output; + + 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/PluginInstaller/DalamudChangelog.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelog.cs index dd928898f..4f2c70a25 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelog.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelog.cs @@ -1,52 +1,51 @@ using System; using System.Collections.Generic; -namespace Dalamud.Interface.Internal.Windows.PluginInstaller +namespace Dalamud.Interface.Internal.Windows.PluginInstaller; + +/// +/// Class representing a Dalamud changelog. +/// +internal class DalamudChangelog { /// - /// Class representing a Dalamud changelog. + /// Gets the date of the version. /// - internal class DalamudChangelog + public DateTime Date { get; init; } + + /// + /// Gets the relevant version number. + /// + public string Version { get; init; } + + /// + /// Gets the list of changes. + /// + public List Changes { get; init; } + + /// + /// Class representing the relevant changes. + /// + public class DalamudChangelogChange { /// - /// Gets the date of the version. + /// Gets the commit message. + /// + public string Message { get; init; } + + /// + /// Gets the commit author. + /// + public string Author { get; init; } + + /// + /// Gets the commit reference SHA. + /// + public string Sha { get; init; } + + /// + /// Gets the commit datetime. /// public DateTime Date { get; init; } - - /// - /// Gets the relevant version number. - /// - public string Version { get; init; } - - /// - /// Gets the list of changes. - /// - public List Changes { get; init; } - - /// - /// Class representing the relevant changes. - /// - public class DalamudChangelogChange - { - /// - /// Gets the commit message. - /// - public string Message { get; init; } - - /// - /// Gets the commit author. - /// - public string Author { get; init; } - - /// - /// Gets the commit reference SHA. - /// - public string Sha { get; init; } - - /// - /// Gets the commit datetime. - /// - public DateTime Date { get; init; } - } } } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogEntry.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogEntry.cs index 7a060d34f..2ea845ed1 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogEntry.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogEntry.cs @@ -1,47 +1,46 @@ using System; -namespace Dalamud.Interface.Internal.Windows.PluginInstaller +namespace Dalamud.Interface.Internal.Windows.PluginInstaller; + +/// +/// Class representing a Dalamud changelog. +/// +internal class DalamudChangelogEntry : IChangelogEntry { + private readonly DalamudChangelog changelog; + /// - /// Class representing a Dalamud changelog. + /// Initializes a new instance of the class. /// - internal class DalamudChangelogEntry : IChangelogEntry + /// The changelog. + public DalamudChangelogEntry(DalamudChangelog changelog) { - private readonly DalamudChangelog changelog; + this.changelog = changelog; - /// - /// Initializes a new instance of the class. - /// - /// The changelog. - public DalamudChangelogEntry(DalamudChangelog changelog) + var changelogText = string.Empty; + for (var i = 0; i < changelog.Changes.Count; i++) { - this.changelog = changelog; + var change = changelog.Changes[i]; + changelogText += $"{change.Message} (by {change.Author})"; - var changelogText = string.Empty; - for (var i = 0; i < changelog.Changes.Count; i++) + if (i < changelog.Changes.Count - 1) { - var change = changelog.Changes[i]; - changelogText += $"{change.Message} (by {change.Author})"; - - if (i < changelog.Changes.Count - 1) - { - changelogText += Environment.NewLine; - } + changelogText += Environment.NewLine; } - - this.Text = changelogText; } - /// - public string Title => "Dalamud Core"; - - /// - public string Version => this.changelog.Version; - - /// - public string Text { get; init; } - - /// - public DateTime Date => this.changelog.Date; + this.Text = changelogText; } + + /// + public string Title => "Dalamud Core"; + + /// + public string Version => this.changelog.Version; + + /// + public string Text { get; init; } + + /// + public DateTime Date => this.changelog.Date; } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs index f0fef8163..086a389d3 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/DalamudChangelogManager.cs @@ -4,35 +4,34 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; -namespace Dalamud.Interface.Internal.Windows.PluginInstaller +namespace Dalamud.Interface.Internal.Windows.PluginInstaller; + +/// +/// Class responsible for managing Dalamud changelogs. +/// +internal class DalamudChangelogManager : IDisposable { + private const string ChangelogUrl = "https://kamori.goats.dev/Plugin/CoreChangelog"; + + private readonly HttpClient client = new(); + /// - /// Class responsible for managing Dalamud changelogs. + /// Gets a list of all available changelogs. /// - internal class DalamudChangelogManager : IDisposable + public IReadOnlyList? Changelogs { get; private set; } + + /// + /// Reload the changelog list. + /// + /// A representing the asynchronous operation. + public async Task ReloadChangelogAsync() { - private const string ChangelogUrl = "https://kamori.goats.dev/Plugin/CoreChangelog"; + this.Changelogs = await this.client.GetFromJsonAsync>(ChangelogUrl); + } - private readonly HttpClient client = new(); - - /// - /// Gets a list of all available changelogs. - /// - public IReadOnlyList? Changelogs { get; private set; } - - /// - /// Reload the changelog list. - /// - /// A representing the asynchronous operation. - public async Task ReloadChangelogAsync() - { - this.Changelogs = await this.client.GetFromJsonAsync>(ChangelogUrl); - } - - /// - public void Dispose() - { - this.client.Dispose(); - } + /// + public void Dispose() + { + this.client.Dispose(); } } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/IChangelogEntry.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/IChangelogEntry.cs index 21143e5c0..04089f23c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/IChangelogEntry.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/IChangelogEntry.cs @@ -1,30 +1,29 @@ using System; -namespace Dalamud.Interface.Internal.Windows.PluginInstaller +namespace Dalamud.Interface.Internal.Windows.PluginInstaller; + +/// +/// Class representing a changelog entry. +/// +internal interface IChangelogEntry { /// - /// Class representing a changelog entry. + /// Gets the title of the entry. /// - internal interface IChangelogEntry - { - /// - /// Gets the title of the entry. - /// - string Title { get; } + string Title { get; } - /// - /// Gets the version this entry applies to. - /// - string Version { get; } + /// + /// Gets the version this entry applies to. + /// + string Version { get; } - /// - /// Gets the text of the entry. - /// - string Text { get; } + /// + /// Gets the text of the entry. + /// + string Text { get; } - /// - /// Gets the date of the entry. - /// - DateTime Date { get; } - } + /// + /// Gets the date of the entry. + /// + DateTime Date { get; } } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs index 1133090e0..90e78b35a 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs @@ -3,44 +3,43 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; -namespace Dalamud.Interface.Internal.Windows.PluginInstaller +namespace Dalamud.Interface.Internal.Windows.PluginInstaller; + +/// +/// Class representing a plugin changelog. +/// +internal class PluginChangelogEntry : IChangelogEntry { /// - /// Class representing a plugin changelog. + /// Initializes a new instance of the class. /// - internal class PluginChangelogEntry : IChangelogEntry + /// The plugin manifest. + public PluginChangelogEntry(LocalPlugin plugin) { - /// - /// Initializes a new instance of the class. - /// - /// The plugin manifest. - public PluginChangelogEntry(LocalPlugin plugin) - { - this.Plugin = plugin; + this.Plugin = plugin; - if (plugin.Manifest.Changelog.IsNullOrEmpty()) - throw new ArgumentException("Manifest has no changelog."); + if (plugin.Manifest.Changelog.IsNullOrEmpty()) + throw new ArgumentException("Manifest has no changelog."); - var version = plugin.Manifest.EffectiveVersion; + var version = plugin.Manifest.EffectiveVersion; - this.Version = version!.ToString(); - } - - /// - /// Gets the respective plugin. - /// - public LocalPlugin Plugin { get; private set; } - - /// - public string Title => this.Plugin.Manifest.Name; - - /// - public string Version { get; init; } - - /// - public string Text => this.Plugin.Manifest.Changelog!; - - /// - public DateTime Date => DateTimeOffset.FromUnixTimeSeconds(this.Plugin.Manifest.LastUpdate).DateTime; + this.Version = version!.ToString(); } + + /// + /// Gets the respective plugin. + /// + public LocalPlugin Plugin { get; private set; } + + /// + public string Title => this.Plugin.Manifest.Name; + + /// + public string Version { get; init; } + + /// + public string Text => this.Plugin.Manifest.Changelog!; + + /// + public DateTime Date => DateTimeOffset.FromUnixTimeSeconds(this.Plugin.Manifest.LastUpdate).DateTime; } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index f48417ea0..c48944533 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -26,773 +26,773 @@ using Dalamud.Utility; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows.PluginInstaller +namespace Dalamud.Interface.Internal.Windows.PluginInstaller; + +/// +/// 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 PluginImageCache imageCache; + private readonly PluginCategoryManager categoryManager = new(); + private readonly DalamudChangelogManager dalamudChangelogManager = new(); + + private readonly List openPluginCollapsibles = new(); + + private readonly DateTime timeLoaded; + + #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 TaskCompletionSource? errorModalTaskCompletionSource; + + private bool updateModalDrawing = true; + private bool updateModalOnNextFrame = false; + private LocalPlugin? updateModalPlugin = null; + private TaskCompletionSource? updateModalTaskCompletionSource; + + private bool feedbackModalDrawing = true; + private bool feedbackModalOnNextFrame = false; + private bool feedbackModalOnNextFrameDontClear = false; + private string feedbackModalBody = string.Empty; + private string feedbackModalContact = string.Empty; + private bool feedbackModalIncludeException = false; + private PluginManifest? feedbackPlugin = null; + private bool feedbackIsTesting = false; + private bool feedbackIsAnonymous = false; + + 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 OperationStatus enableDisableStatus = OperationStatus.Idle; + + private LoadingIndicatorKind loadingIndicatorKind = LoadingIndicatorKind.Unknown; + /// - /// Class responsible for drawing the plugin installer. + /// Initializes a new instance of the class. /// - internal class PluginInstallerWindow : Window, IDisposable + /// An instance of class. + public PluginInstallerWindow(PluginImageCache imageCache) + : 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; + this.imageCache = imageCache; - 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 PluginImageCache imageCache; - private readonly PluginCategoryManager categoryManager = new(); - private readonly DalamudChangelogManager dalamudChangelogManager = new(); - - private readonly List openPluginCollapsibles = new(); - - private readonly DateTime timeLoaded; - - #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 TaskCompletionSource? errorModalTaskCompletionSource; - - private bool updateModalDrawing = true; - private bool updateModalOnNextFrame = false; - private LocalPlugin? updateModalPlugin = null; - private TaskCompletionSource? updateModalTaskCompletionSource; - - private bool feedbackModalDrawing = true; - private bool feedbackModalOnNextFrame = false; - private bool feedbackModalOnNextFrameDontClear = false; - private string feedbackModalBody = string.Empty; - private string feedbackModalContact = string.Empty; - private bool feedbackModalIncludeException = false; - private PluginManifest? feedbackPlugin = null; - private bool feedbackIsTesting = false; - private bool feedbackIsAnonymous = false; - - 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 OperationStatus enableDisableStatus = OperationStatus.Idle; - - private LoadingIndicatorKind loadingIndicatorKind = LoadingIndicatorKind.Unknown; - - /// - /// Initializes a new instance of the class. - /// - /// An instance of class. - public PluginInstallerWindow(PluginImageCache imageCache) - : base( - Locs.WindowTitle + (Service.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller", - ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar) + this.SizeConstraints = new WindowSizeConstraints { - this.IsOpen = true; - this.imageCache = imageCache; + MinimumSize = this.Size.Value, + MaximumSize = new Vector2(5000, 5000), + }; - this.Size = new Vector2(830, 570); - this.SizeCondition = ImGuiCond.FirstUseEver; + Service.GetAsync().ContinueWith(pluginManagerTask => + { + var pluginManager = pluginManagerTask.Result; - this.SizeConstraints = new WindowSizeConstraints + // For debugging + if (pluginManager.PluginsReady) + this.OnInstalledPluginsChanged(); + + pluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged; + pluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged; + + for (var i = 0; i < this.testerImagePaths.Length; i++) { - MinimumSize = this.Size.Value, - MaximumSize = new Vector2(5000, 5000), - }; - - Service.GetAsync().ContinueWith(pluginManagerTask => - { - var pluginManager = pluginManagerTask.Result; - - // 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; - } - }); - - this.timeLoaded = DateTime.Now; - } - - private enum OperationStatus - { - Idle, - InProgress, - Complete, - } - - private enum LoadingIndicatorKind - { - Unknown, - EnablingSingle, - DisablingSingle, - UpdatingSingle, - UpdatingAll, - Installing, - Manager, - } - - private enum PluginSortKind - { - Alphabetical, - DownloadCount, - LastUpdate, - NewOrNot, - NotInstalled, - } - - private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress || - this.updateStatus == OperationStatus.InProgress || - this.enableDisableStatus == OperationStatus.InProgress; - - /// - public void Dispose() - { - var pluginManager = Service.GetNullable(); - if (pluginManager != null) - { - pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged; - pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged; + this.testerImagePaths[i] = string.Empty; } - } + }); - /// - public override void OnOpen() + this.timeLoaded = DateTime.Now; + } + + private enum OperationStatus + { + Idle, + InProgress, + Complete, + } + + private enum LoadingIndicatorKind + { + Unknown, + EnablingSingle, + DisablingSingle, + UpdatingSingle, + UpdatingAll, + Installing, + Manager, + } + + private enum PluginSortKind + { + Alphabetical, + DownloadCount, + LastUpdate, + NewOrNot, + NotInstalled, + } + + private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress || + this.updateStatus == OperationStatus.InProgress || + this.enableDisableStatus == OperationStatus.InProgress; + + /// + public void Dispose() + { + var pluginManager = Service.GetNullable(); + if (pluginManager != null) { - var pluginManager = Service.Get(); + pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged; + pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged; + } + } - _ = pluginManager.ReloadPluginMastersAsync(); - _ = this.dalamudChangelogManager.ReloadChangelogAsync(); + /// + public override void OnOpen() + { + var pluginManager = Service.Get(); - this.searchText = string.Empty; - this.sortKind = PluginSortKind.Alphabetical; - this.filterText = Locs.SortBy_Alphabetical; + _ = pluginManager.ReloadPluginMastersAsync(); + _ = this.dalamudChangelogManager.ReloadChangelogAsync(); - if (this.updateStatus == OperationStatus.Complete || this.updateStatus == OperationStatus.Idle) + this.searchText = string.Empty; + this.sortKind = PluginSortKind.Alphabetical; + this.filterText = Locs.SortBy_Alphabetical; + + if (this.updateStatus == OperationStatus.Complete || this.updateStatus == OperationStatus.Idle) + { + this.updateStatus = OperationStatus.Idle; + this.updatePluginCount = 0; + this.updatedPlugins = null; + } + } + + /// + public override void OnClose() + { + Service.Get().Save(); + } + + /// + public override void Draw() + { + this.DrawHeader(); + this.DrawPluginCategories(); + this.DrawFooter(); + this.DrawErrorModal(); + this.DrawUpdateModal(); + this.DrawFeedbackModal(); + this.DrawProgressOverlay(); + } + + /// + /// Clear the icon and image caches, forcing a fresh download. + /// + public void ClearIconCache() + { + this.imageCache.ClearIconCache(); + } + + /// + /// Open the window on the plugin changelogs. + /// + public void OpenPluginChangelogs() + { + // Changelog group + this.categoryManager.CurrentGroupIdx = 3; + // Plugins category + this.categoryManager.CurrentCategoryIdx = 2; + this.IsOpen = true; + } + + private void DrawProgressOverlay() + { + var pluginManager = Service.Get(); + + var isWaitingManager = !pluginManager.PluginsReady || + !pluginManager.ReposReady; + var isLoading = this.AnyOperationInProgress || + isWaitingManager; + + if (isWaitingManager) + this.loadingIndicatorKind = LoadingIndicatorKind.Manager; + + if (!isLoading) + return; + + ImGui.SetCursorPos(Vector2.Zero); + + var windowSize = ImGui.GetWindowSize(); + var titleHeight = ImGui.GetFontSize() + (ImGui.GetStyle().FramePadding.Y * 2); + + if (ImGui.BeginChild("###installerLoadingFrame", new Vector2(-1, -1), false)) + { + ImGui.GetWindowDrawList().PushClipRectFullScreen(); + ImGui.GetWindowDrawList().AddRectFilled( + ImGui.GetWindowPos() + new Vector2(0, titleHeight), + ImGui.GetWindowPos() + windowSize, + 0xCC000000, + ImGui.GetStyle().WindowRounding, + ImDrawFlags.RoundCornersBottom); + ImGui.PopClipRect(); + + ImGui.SetCursorPosY(windowSize.Y / 2); + + switch (this.loadingIndicatorKind) { - this.updateStatus = OperationStatus.Idle; - this.updatePluginCount = 0; - this.updatedPlugins = null; - } - } - - /// - public override void OnClose() - { - Service.Get().Save(); - } - - /// - public override void Draw() - { - this.DrawHeader(); - this.DrawPluginCategories(); - this.DrawFooter(); - this.DrawErrorModal(); - this.DrawUpdateModal(); - this.DrawFeedbackModal(); - this.DrawProgressOverlay(); - } - - /// - /// Clear the icon and image caches, forcing a fresh download. - /// - public void ClearIconCache() - { - this.imageCache.ClearIconCache(); - } - - /// - /// Open the window on the plugin changelogs. - /// - public void OpenPluginChangelogs() - { - // Changelog group - this.categoryManager.CurrentGroupIdx = 3; - // Plugins category - this.categoryManager.CurrentCategoryIdx = 2; - this.IsOpen = true; - } - - private void DrawProgressOverlay() - { - var pluginManager = Service.Get(); - - var isWaitingManager = !pluginManager.PluginsReady || - !pluginManager.ReposReady; - var isLoading = this.AnyOperationInProgress || - isWaitingManager; - - if (isWaitingManager) - this.loadingIndicatorKind = LoadingIndicatorKind.Manager; - - if (!isLoading) - return; - - ImGui.SetCursorPos(Vector2.Zero); - - var windowSize = ImGui.GetWindowSize(); - var titleHeight = ImGui.GetFontSize() + (ImGui.GetStyle().FramePadding.Y * 2); - - if (ImGui.BeginChild("###installerLoadingFrame", new Vector2(-1, -1), false)) - { - ImGui.GetWindowDrawList().PushClipRectFullScreen(); - ImGui.GetWindowDrawList().AddRectFilled( - ImGui.GetWindowPos() + new Vector2(0, titleHeight), - ImGui.GetWindowPos() + windowSize, - 0xCC000000, - ImGui.GetStyle().WindowRounding, - ImDrawFlags.RoundCornersBottom); - ImGui.PopClipRect(); - - ImGui.SetCursorPosY(windowSize.Y / 2); - - switch (this.loadingIndicatorKind) - { - case LoadingIndicatorKind.Unknown: - ImGuiHelpers.CenteredText("Doing something, not sure what!"); - break; - case LoadingIndicatorKind.EnablingSingle: - ImGuiHelpers.CenteredText("Enabling plugin..."); - break; - case LoadingIndicatorKind.DisablingSingle: - ImGuiHelpers.CenteredText("Disabling plugin..."); - break; - case LoadingIndicatorKind.UpdatingSingle: - ImGuiHelpers.CenteredText("Updating plugin..."); - break; - case LoadingIndicatorKind.UpdatingAll: - ImGuiHelpers.CenteredText("Updating plugins..."); - break; - case LoadingIndicatorKind.Installing: - ImGuiHelpers.CenteredText("Installing plugin..."); - break; - case LoadingIndicatorKind.Manager: + case LoadingIndicatorKind.Unknown: + ImGuiHelpers.CenteredText("Doing something, not sure what!"); + break; + case LoadingIndicatorKind.EnablingSingle: + ImGuiHelpers.CenteredText("Enabling plugin..."); + break; + case LoadingIndicatorKind.DisablingSingle: + ImGuiHelpers.CenteredText("Disabling plugin..."); + break; + case LoadingIndicatorKind.UpdatingSingle: + ImGuiHelpers.CenteredText("Updating plugin..."); + break; + case LoadingIndicatorKind.UpdatingAll: + ImGuiHelpers.CenteredText("Updating plugins..."); + break; + case LoadingIndicatorKind.Installing: + ImGuiHelpers.CenteredText("Installing plugin..."); + break; + case LoadingIndicatorKind.Manager: + { + if (pluginManager.PluginsReady && !pluginManager.ReposReady) { - if (pluginManager.PluginsReady && !pluginManager.ReposReady) - { - ImGuiHelpers.CenteredText("Loading repositories..."); - } - else if (!pluginManager.PluginsReady && pluginManager.ReposReady) - { - ImGuiHelpers.CenteredText("Loading installed plugins..."); - } - else - { - ImGuiHelpers.CenteredText("Loading repositories and plugins..."); - } - - var currentProgress = 0; - var total = 0; - - var pendingRepos = pluginManager.Repos.ToArray() - .Where(x => (x.State != PluginRepositoryState.Success && - x.State != PluginRepositoryState.Fail) && - x.IsEnabled) - .ToArray(); - var allRepoCount = - pluginManager.Repos.Count(x => x.State != PluginRepositoryState.Fail && x.IsEnabled); - - foreach (var repo in pendingRepos) - { - ImGuiHelpers.CenteredText($"{repo.PluginMasterUrl}: {repo.State}"); - } - - currentProgress += allRepoCount - pendingRepos.Length; - total += allRepoCount; - - if (currentProgress != total) - { - ImGui.SetCursorPosX(windowSize.X / 3); - ImGui.ProgressBar(currentProgress / (float)total, new Vector2(windowSize.X / 3, 50)); - } + ImGuiHelpers.CenteredText("Loading repositories..."); } - - break; - default: - throw new ArgumentOutOfRangeException(); - } - - if (DateTime.Now - this.timeLoaded > TimeSpan.FromSeconds(90) && !pluginManager.PluginsReady) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGuiHelpers.CenteredText("This is embarrassing, but..."); - ImGuiHelpers.CenteredText("one of your plugins may be blocking the installer."); - ImGuiHelpers.CenteredText("You should tell us about this, please keep this window open."); - ImGui.PopStyleColor(); - } - - ImGui.EndChild(); - } - } - - private void DrawHeader() - { - var style = ImGui.GetStyle(); - var windowSize = ImGui.GetWindowContentRegionMax(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); - - var searchInputWidth = 180 * ImGuiHelpers.GlobalScale; - var searchClearButtonWidth = 25 * 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), - (Locs.SortBy_NotInstalled, PluginSortKind.NotInstalled), - }; - 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); - - ImGui.SameLine(); - - // Shift down a little to align with the middle of the header text - var downShift = ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2; - ImGui.SetCursorPosY(downShift); - - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth); - - var searchTextChanged = false; - ImGui.SetNextItemWidth(searchInputWidth); - searchTextChanged |= ImGui.InputTextWithHint( - "###XlPluginInstaller_Search", - Locs.Header_SearchPlaceholder, - ref this.searchText, - 100); - - ImGui.SameLine(); - ImGui.SetCursorPosY(downShift); - - ImGui.SetNextItemWidth(searchClearButtonWidth); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Times)) - { - this.searchText = string.Empty; - searchTextChanged = true; - } - - if (searchTextChanged) - this.UpdateCategoriesOnSearchChange(); - - // Changelog group - var isSortDisabled = this.categoryManager.CurrentGroupIdx == 3; - if (isSortDisabled) - ImGui.BeginDisabled(); - - ImGui.SameLine(); - ImGui.SetCursorPosY(downShift); - 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(); - } - - if (isSortDisabled) - ImGui.EndDisabled(); - } - - 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(); - 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(); - } - } - - private void DrawUpdatePluginsButton() - { - var pluginManager = Service.Get(); - var notifications = Service.Get(); - - var ready = pluginManager.PluginsReady && pluginManager.ReposReady; - - if (pluginManager.SafeMode) - { - ImGuiComponents.DisabledButton(Locs.FooterButton_UpdateSafeMode); - } - else 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; - this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; - - Task.Run(() => pluginManager.UpdatePluginsAsync(true, false)) - .ContinueWith(task => + else if (!pluginManager.PluginsReady && pluginManager.ReposReady) { - 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) - { - Service.Get().PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); - notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); - - var installedGroupIdx = this.categoryManager.GroupList.TakeWhile( - x => x.GroupKind != PluginCategoryManager.GroupKind.Installed).Count(); - this.categoryManager.CurrentGroupIdx = installedGroupIdx; - } - else if (this.updatePluginCount == 0) - { - notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info); - } - } - }); - } - } - } - - 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))) - { - ImGui.CloseCurrentPopup(); - this.errorModalTaskCompletionSource?.SetResult(); - } - - ImGui.EndPopup(); - } - - if (this.errorModalOnNextFrame) - { - // NOTE(goat): ImGui cannot open a modal if no window is focused, at the moment. - // If people click out of the installer into the game while a plugin is installing, we won't be able to show a modal if we don't grab focus. - ImGui.SetWindowFocus(this.WindowName); - - ImGui.OpenPopup(modalTitle); - this.errorModalOnNextFrame = false; - this.errorModalDrawing = true; - } - } - - private void DrawUpdateModal() - { - var modalTitle = Locs.UpdateModal_Title; - - if (this.updateModalPlugin == null) - return; - - if (ImGui.BeginPopupModal(modalTitle, ref this.updateModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.Text(Locs.UpdateModal_UpdateAvailable(this.updateModalPlugin.Name)); - ImGui.Spacing(); - - var buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - ((buttonWidth * 2) - (ImGui.GetStyle().ItemSpacing.Y * 2))) / 2); - - if (ImGui.Button(Locs.UpdateModal_Yes, new Vector2(buttonWidth, 40))) - { - ImGui.CloseCurrentPopup(); - this.updateModalTaskCompletionSource?.SetResult(true); - } - - ImGui.SameLine(); - - if (ImGui.Button(Locs.UpdateModal_No, new Vector2(buttonWidth, 40))) - { - ImGui.CloseCurrentPopup(); - this.updateModalTaskCompletionSource?.SetResult(false); - } - - ImGui.EndPopup(); - } - - if (this.updateModalOnNextFrame) - { - // NOTE(goat): ImGui cannot open a modal if no window is focused, at the moment. - // If people click out of the installer into the game while a plugin is installing, we won't be able to show a modal if we don't grab focus. - ImGui.SetWindowFocus(this.WindowName); - - ImGui.OpenPopup(modalTitle); - this.updateModalOnNextFrame = false; - this.updateModalDrawing = true; - } - } - - private void DrawFeedbackModal() - { - var modalTitle = Locs.FeedbackModal_Title; - - if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.TextUnformatted(Locs.FeedbackModal_Text(this.feedbackPlugin.Name)); - - if (this.feedbackPlugin?.FeedbackMessage != null) - { - ImGuiHelpers.SafeTextWrapped(this.feedbackPlugin.FeedbackMessage); - } - - 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(); - - if (ImGui.Checkbox(Locs.FeedbackModal_ContactAnonymous, ref this.feedbackIsAnonymous)) - { - if (this.feedbackIsAnonymous) - this.feedbackModalContact = string.Empty; - } - - if (this.feedbackIsAnonymous) - { - ImGui.BeginDisabled(); - ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 0); - ImGui.EndDisabled(); - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.Text(Locs.FeedbackModal_ContactAnonymousWarning); - ImGui.PopStyleColor(); - } - else - { - ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); - - ImGui.SameLine(); - - if (ImGui.Button(Locs.FeedbackModal_ContactInformationDiscordButton)) - { - Process.Start(new ProcessStartInfo(Locs.FeedbackModal_ContactInformationDiscordUrl) - { - UseShellExecute = true, - }); - } - - ImGui.Text(Locs.FeedbackModal_ContactInformationHelp); - - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.Text(Locs.FeedbackModal_ContactInformationWarning); - ImGui.PopStyleColor(); - } - - ImGui.Spacing(); - - 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.feedbackIsAnonymous && string.IsNullOrWhiteSpace(this.feedbackModalContact)) - { - this.ShowErrorModal(Locs.FeedbackModal_ContactInformationRequired) - .ContinueWith(_ => - { - this.feedbackModalOnNextFrameDontClear = true; - this.feedbackModalOnNextFrame = true; - }); - } - else - { - if (this.feedbackPlugin != null) - { - Task.Run(async () => await BugBait.SendFeedback( - this.feedbackPlugin, - this.feedbackIsTesting, - 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); - } - }); + ImGuiHelpers.CenteredText("Loading installed plugins..."); } else { - Log.Error("FeedbackPlugin was null."); + ImGuiHelpers.CenteredText("Loading repositories and plugins..."); } - if (!string.IsNullOrWhiteSpace(this.feedbackModalContact)) + var currentProgress = 0; + var total = 0; + + var pendingRepos = pluginManager.Repos.ToArray() + .Where(x => (x.State != PluginRepositoryState.Success && + x.State != PluginRepositoryState.Fail) && + x.IsEnabled) + .ToArray(); + var allRepoCount = + pluginManager.Repos.Count(x => x.State != PluginRepositoryState.Fail && x.IsEnabled); + + foreach (var repo in pendingRepos) { - Service.Get().LastFeedbackContactDetails = this.feedbackModalContact; + ImGuiHelpers.CenteredText($"{repo.PluginMasterUrl}: {repo.State}"); } - ImGui.CloseCurrentPopup(); - } - } + currentProgress += allRepoCount - pendingRepos.Length; + total += allRepoCount; - ImGui.EndPopup(); + if (currentProgress != total) + { + ImGui.SetCursorPosX(windowSize.X / 3); + ImGui.ProgressBar(currentProgress / (float)total, new Vector2(windowSize.X / 3, 50)); + } + } + + break; + default: + throw new ArgumentOutOfRangeException(); } - if (this.feedbackModalOnNextFrame) + if (DateTime.Now - this.timeLoaded > TimeSpan.FromSeconds(90) && !pluginManager.PluginsReady) { - ImGui.OpenPopup(modalTitle); - this.feedbackModalOnNextFrame = false; - this.feedbackModalDrawing = true; - if (!this.feedbackModalOnNextFrameDontClear) + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGuiHelpers.CenteredText("This is embarrassing, but..."); + ImGuiHelpers.CenteredText("one of your plugins may be blocking the installer."); + ImGuiHelpers.CenteredText("You should tell us about this, please keep this window open."); + ImGui.PopStyleColor(); + } + + ImGui.EndChild(); + } + } + + private void DrawHeader() + { + var style = ImGui.GetStyle(); + var windowSize = ImGui.GetWindowContentRegionMax(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); + + var searchInputWidth = 180 * ImGuiHelpers.GlobalScale; + var searchClearButtonWidth = 25 * 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), + (Locs.SortBy_NotInstalled, PluginSortKind.NotInstalled), + }; + 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); + + ImGui.SameLine(); + + // Shift down a little to align with the middle of the header text + var downShift = ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2; + ImGui.SetCursorPosY(downShift); + + ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth); + + var searchTextChanged = false; + ImGui.SetNextItemWidth(searchInputWidth); + searchTextChanged |= ImGui.InputTextWithHint( + "###XlPluginInstaller_Search", + Locs.Header_SearchPlaceholder, + ref this.searchText, + 100); + + ImGui.SameLine(); + ImGui.SetCursorPosY(downShift); + + ImGui.SetNextItemWidth(searchClearButtonWidth); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Times)) + { + this.searchText = string.Empty; + searchTextChanged = true; + } + + if (searchTextChanged) + this.UpdateCategoriesOnSearchChange(); + + // Changelog group + var isSortDisabled = this.categoryManager.CurrentGroupIdx == 3; + if (isSortDisabled) + ImGui.BeginDisabled(); + + ImGui.SameLine(); + ImGui.SetCursorPosY(downShift); + ImGui.SetNextItemWidth(selectableWidth); + if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton)) + { + foreach (var selectable in sortSelectables) + { + if (ImGui.Selectable(selectable.Localization)) { - this.feedbackModalBody = string.Empty; - this.feedbackModalContact = Service.Get().LastFeedbackContactDetails; - this.feedbackModalIncludeException = false; - this.feedbackIsAnonymous = false; - } - else - { - this.feedbackModalOnNextFrameDontClear = false; + this.sortKind = selectable.SortKind; + this.filterText = selectable.Localization; + + this.ResortPlugins(); } } + + ImGui.EndCombo(); + } + + if (isSortDisabled) + ImGui.EndDisabled(); + } + + 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(); + if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) + { + pluginManager.ScanDevPlugins(); + } } - private void DrawChangelogList(bool displayDalamud, bool displayPlugins) + var closeText = Locs.FooterButton_Close; + var closeButtonSize = ImGuiHelpers.GetButtonSize(closeText); + + ImGui.SameLine(windowSize.X - closeButtonSize.X - 20); + if (ImGui.Button(closeText)) { - if (this.pluginListInstalled.Count == 0) + this.IsOpen = false; + configuration.Save(); + } + } + + private void DrawUpdatePluginsButton() + { + var pluginManager = Service.Get(); + var notifications = Service.Get(); + + var ready = pluginManager.PluginsReady && pluginManager.ReposReady; + + if (pluginManager.SafeMode) + { + ImGuiComponents.DisabledButton(Locs.FooterButton_UpdateSafeMode); + } + else 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)) { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); - return; + this.updateStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; + + Task.Run(() => pluginManager.UpdatePluginsAsync(true, false)) + .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) + { + Service.Get().PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); + notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); + + var installedGroupIdx = this.categoryManager.GroupList.TakeWhile( + x => x.GroupKind != PluginCategoryManager.GroupKind.Installed).Count(); + this.categoryManager.CurrentGroupIdx = installedGroupIdx; + } + else if (this.updatePluginCount == 0) + { + notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info); + } + } + }); + } + } + } + + 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))) + { + ImGui.CloseCurrentPopup(); + this.errorModalTaskCompletionSource?.SetResult(); } - var pluginChangelogs = this.pluginListInstalled + ImGui.EndPopup(); + } + + if (this.errorModalOnNextFrame) + { + // NOTE(goat): ImGui cannot open a modal if no window is focused, at the moment. + // If people click out of the installer into the game while a plugin is installing, we won't be able to show a modal if we don't grab focus. + ImGui.SetWindowFocus(this.WindowName); + + ImGui.OpenPopup(modalTitle); + this.errorModalOnNextFrame = false; + this.errorModalDrawing = true; + } + } + + private void DrawUpdateModal() + { + var modalTitle = Locs.UpdateModal_Title; + + if (this.updateModalPlugin == null) + return; + + if (ImGui.BeginPopupModal(modalTitle, ref this.updateModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(Locs.UpdateModal_UpdateAvailable(this.updateModalPlugin.Name)); + ImGui.Spacing(); + + var buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - ((buttonWidth * 2) - (ImGui.GetStyle().ItemSpacing.Y * 2))) / 2); + + if (ImGui.Button(Locs.UpdateModal_Yes, new Vector2(buttonWidth, 40))) + { + ImGui.CloseCurrentPopup(); + this.updateModalTaskCompletionSource?.SetResult(true); + } + + ImGui.SameLine(); + + if (ImGui.Button(Locs.UpdateModal_No, new Vector2(buttonWidth, 40))) + { + ImGui.CloseCurrentPopup(); + this.updateModalTaskCompletionSource?.SetResult(false); + } + + ImGui.EndPopup(); + } + + if (this.updateModalOnNextFrame) + { + // NOTE(goat): ImGui cannot open a modal if no window is focused, at the moment. + // If people click out of the installer into the game while a plugin is installing, we won't be able to show a modal if we don't grab focus. + ImGui.SetWindowFocus(this.WindowName); + + ImGui.OpenPopup(modalTitle); + this.updateModalOnNextFrame = false; + this.updateModalDrawing = true; + } + } + + private void DrawFeedbackModal() + { + var modalTitle = Locs.FeedbackModal_Title; + + if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.TextUnformatted(Locs.FeedbackModal_Text(this.feedbackPlugin.Name)); + + if (this.feedbackPlugin?.FeedbackMessage != null) + { + ImGuiHelpers.SafeTextWrapped(this.feedbackPlugin.FeedbackMessage); + } + + 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(); + + if (ImGui.Checkbox(Locs.FeedbackModal_ContactAnonymous, ref this.feedbackIsAnonymous)) + { + if (this.feedbackIsAnonymous) + this.feedbackModalContact = string.Empty; + } + + if (this.feedbackIsAnonymous) + { + ImGui.BeginDisabled(); + ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 0); + ImGui.EndDisabled(); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.Text(Locs.FeedbackModal_ContactAnonymousWarning); + ImGui.PopStyleColor(); + } + else + { + ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); + + ImGui.SameLine(); + + if (ImGui.Button(Locs.FeedbackModal_ContactInformationDiscordButton)) + { + Process.Start(new ProcessStartInfo(Locs.FeedbackModal_ContactInformationDiscordUrl) + { + UseShellExecute = true, + }); + } + + ImGui.Text(Locs.FeedbackModal_ContactInformationHelp); + + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.Text(Locs.FeedbackModal_ContactInformationWarning); + ImGui.PopStyleColor(); + } + + ImGui.Spacing(); + + 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.feedbackIsAnonymous && string.IsNullOrWhiteSpace(this.feedbackModalContact)) + { + this.ShowErrorModal(Locs.FeedbackModal_ContactInformationRequired) + .ContinueWith(_ => + { + this.feedbackModalOnNextFrameDontClear = true; + this.feedbackModalOnNextFrame = true; + }); + } + else + { + if (this.feedbackPlugin != null) + { + Task.Run(async () => await BugBait.SendFeedback( + this.feedbackPlugin, + this.feedbackIsTesting, + 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."); + } + + if (!string.IsNullOrWhiteSpace(this.feedbackModalContact)) + { + Service.Get().LastFeedbackContactDetails = this.feedbackModalContact; + } + + ImGui.CloseCurrentPopup(); + } + } + + ImGui.EndPopup(); + } + + if (this.feedbackModalOnNextFrame) + { + ImGui.OpenPopup(modalTitle); + this.feedbackModalOnNextFrame = false; + this.feedbackModalDrawing = true; + if (!this.feedbackModalOnNextFrameDontClear) + { + this.feedbackModalBody = string.Empty; + this.feedbackModalContact = Service.Get().LastFeedbackContactDetails; + this.feedbackModalIncludeException = false; + this.feedbackIsAnonymous = false; + } + else + { + this.feedbackModalOnNextFrameDontClear = false; + } + } + } + + private void DrawChangelogList(bool displayDalamud, bool displayPlugins) + { + if (this.pluginListInstalled.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; + } + + var pluginChangelogs = this.pluginListInstalled .Where(plugin => !this.IsManifestFiltered(plugin.Manifest) && !plugin.Manifest.Changelog.IsNullOrEmpty()) .Select(x => @@ -801,1685 +801,424 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller return (IChangelogEntry)changelog; }); - IEnumerable changelogs = null; - if (displayDalamud && displayPlugins && this.dalamudChangelogManager.Changelogs != null) - { - changelogs = pluginChangelogs - .Concat(this.dalamudChangelogManager.Changelogs.Select( - x => new DalamudChangelogEntry(x))); - } - else if (displayDalamud && this.dalamudChangelogManager.Changelogs != null) - { - changelogs = this.dalamudChangelogManager.Changelogs.Select( - x => new DalamudChangelogEntry(x)); - } - else if (displayPlugins) - { - changelogs = pluginChangelogs; - } - - var sortedChangelogs = changelogs?.OrderByDescending(x => x.Date).ToList(); - - if (sortedChangelogs == null || !sortedChangelogs.Any()) - { - ImGui.TextColored( - ImGuiColors.DalamudGrey2, - this.pluginListInstalled.Any(plugin => !plugin.Manifest.Changelog.IsNullOrEmpty()) - ? Locs.TabBody_SearchNoMatching - : Locs.TabBody_ChangelogNone); - - return; - } - - foreach (var logEntry in sortedChangelogs) - { - this.DrawChangelog(logEntry); - } + IEnumerable changelogs = null; + if (displayDalamud && displayPlugins && this.dalamudChangelogManager.Changelogs != null) + { + changelogs = pluginChangelogs + .Concat(this.dalamudChangelogManager.Changelogs.Select( + x => new DalamudChangelogEntry(x))); + } + else if (displayDalamud && this.dalamudChangelogManager.Changelogs != null) + { + changelogs = this.dalamudChangelogManager.Changelogs.Select( + x => new DalamudChangelogEntry(x)); + } + else if (displayPlugins) + { + changelogs = pluginChangelogs; } - private void DrawAvailablePluginList() + var sortedChangelogs = changelogs?.OrderByDescending(x => x.Date).ToList(); + + if (sortedChangelogs == null || !sortedChangelogs.Any()) { - var pluginList = this.pluginListAvailable; + ImGui.TextColored( + ImGuiColors.DalamudGrey2, + this.pluginListInstalled.Any(plugin => !plugin.Manifest.Changelog.IsNullOrEmpty()) + ? Locs.TabBody_SearchNoMatching + : Locs.TabBody_ChangelogNone); - 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) - { - if (manifest is not RemotePluginManifest remoteManifest) - continue; - 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(); - } + return; } - private void DrawInstalledPluginList() + foreach (var logEntry in sortedChangelogs) { - var pluginList = this.pluginListInstalled; + this.DrawChangelog(logEntry); + } + } - if (pluginList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); - return; - } + private void DrawAvailablePluginList() + { + var pluginList = this.pluginListAvailable; - 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++); - } + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible); + return; } - private void DrawInstalledDevPluginList() + var filteredManifests = pluginList + .Where(rm => !this.IsManifestFiltered(rm)) + .ToList(); + + if (filteredManifests.Count == 0) { - 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++); - } + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; } - private void DrawPluginCategories() + // get list to show and reset category dirty flag + var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests); + + var i = 0; + foreach (var manifest in categoryManifestsList) { - var useContentHeight = -40f; // button height + spacing - var useMenuWidth = 180f; // works fine as static value, table can be resized by user + if (manifest is not RemotePluginManifest remoteManifest) + continue; + var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest); - var useContentWidth = ImGui.GetContentRegionAvail().X; - - if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale))) + ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}"); + if (isInstalled) { - 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() && !this.AnyOperationInProgress; - if (!this.categoryManager.IsSelectionValid || !ready) - { - return; - } - - var pm = Service.Get(); - if (pm.SafeMode) - { - ImGuiHelpers.ScaledDummy(10); - - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); - ImGui.PushFont(InterfaceManager.IconFont); - ImGuiHelpers.CenteredText(FontAwesomeIcon.ExclamationTriangle.ToIconString()); - ImGui.PopFont(); - ImGui.PopStyleColor(); - - var lines = Locs.SafeModeDisclaimer.Split('\n'); - foreach (var line in lines) - { - ImGuiHelpers.CenteredText(line); - } - - ImGuiHelpers.ScaledDummy(10); - ImGui.Separator(); - } - - 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(); - } - } - - switch (groupInfo.GroupKind) - { - case 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; - } - - break; - case PluginCategoryManager.GroupKind.Installed: - this.DrawInstalledPluginList(); - break; - case PluginCategoryManager.GroupKind.Changelog: - switch (this.categoryManager.CurrentCategoryIdx) - { - case 0: - this.DrawChangelogList(true, true); - break; - - case 1: - this.DrawChangelogList(true, false); - break; - - case 2: - this.DrawChangelogList(false, true); - break; - } - - break; - default: - this.DrawAvailablePluginList(); - break; - } - - 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)); + this.DrawInstalledPlugin(plugin, i++, true); } 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); - - static void CheckImageSize(TextureWrap? image, int maxWidth, int maxHeight, bool requireSquare) - { - if (image == null) - return; - if (image.Width > maxWidth || image.Height > maxHeight) - ImGui.TextColored(ImGuiColors.DalamudRed, $"Image is larger than the maximum allowed resolution ({image.Width}x{image.Height} > {maxWidth}x{maxHeight})"); - if (requireSquare && image.Width != image.Height) - ImGui.TextColored(ImGuiColors.DalamudRed, $"Image must be square! Current size: {image.Width}x{image.Height}"); - } - - ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); - if (this.testerIcon != null) - CheckImageSize(this.testerIcon, PluginImageCache.PluginIconWidth, PluginImageCache.PluginIconHeight, true); - ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000); - if (this.testerImages?.Length > 0) - CheckImageSize(this.testerImages[0], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); - ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000); - if (this.testerImages?.Length > 1) - CheckImageSize(this.testerImages[1], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); - ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000); - if (this.testerImages?.Length > 2) - CheckImageSize(this.testerImages[2], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); - ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000); - if (this.testerImages?.Length > 3) - CheckImageSize(this.testerImages[3], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); - ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000); - if (this.testerImages?.Length > 4) - CheckImageSize(this.testerImages[4], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); - - 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(); - - 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, bool installableOutdated, 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 pluginDisabled = plugin is { IsDisabled: true }; - - var iconSize = ImGuiHelpers.ScaledVector2(64, 64); - var cursorBeforeImage = ImGui.GetCursorPos(); - var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos(); - if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize)) - { - var iconTex = this.imageCache.DefaultIcon; - var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); - if (hasIcon && cachedIconTex != null) - { - iconTex = cachedIconTex; - } - - if (pluginDisabled || installableOutdated) - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.4f); - } - - ImGui.Image(iconTex.ImGuiHandle, iconSize); - - if (pluginDisabled || installableOutdated) - { - ImGui.PopStyleVar(); - } - - ImGui.SameLine(); - ImGui.SetCursorPos(cursorBeforeImage); - } - - var isLoaded = plugin is { IsLoaded: true }; - - if (updateAvailable) - ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); - else if (trouble && !pluginDisabled) - ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); - else if (installableOutdated) - ImGui.Image(this.imageCache.OutdatedInstallableIcon.ImGuiHandle, iconSize); - else if (pluginDisabled) - ImGui.Image(this.imageCache.DisabledIcon.ImGuiHandle, iconSize); - else if (isLoaded && isThirdParty) - ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize); - else if (isThirdParty) - ImGui.Image(this.imageCache.ThirdIcon.ImGuiHandle, iconSize); - else if (isLoaded) - ImGui.Image(this.imageCache.InstalledIcon.ImGuiHandle, iconSize); - else - ImGui.Dummy(iconSize); - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - var cursor = ImGui.GetCursorPos(); - - // Name - ImGui.TextUnformatted(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 } || installableOutdated) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_Outdated); - ImGui.PopStyleColor(); - } - else if (plugin is { IsBanned: true }) - { - // Banned warning - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGuiHelpers.SafeTextWrapped(plugin.BanReason.IsNullOrEmpty() - ? Locs.PluginBody_Banned - : Locs.PluginBody_BannedReason(plugin.BanReason)); - - ImGui.PopStyleColor(); - } - else if (plugin is { IsOrphaned: true }) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_Orphaned); - ImGui.PopStyleColor(); - } - else if (plugin != null && !plugin.CheckPolicy()) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_Policy); - ImGui.PopStyleColor(); - } - else if (plugin is { State: PluginState.LoadError or PluginState.DependencyResolutionFailed }) - { - // Load failed warning - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_LoadFailed); - ImGui.PopStyleColor(); - } - - ImGui.SetCursorPosX(cursor.X); - - // Description - if (plugin is null or { IsOutdated: false, IsBanned: false }) - { - if (!string.IsNullOrWhiteSpace(manifest.Punchline)) - { - ImGuiHelpers.SafeTextWrapped(manifest.Punchline); - } - else if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - const int punchlineLen = 200; - var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0]; - - ImGuiHelpers.SafeTextWrapped(firstLine.Length < punchlineLen - ? firstLine - : firstLine[..punchlineLen]); - } - } - - startCursor.Y += sectionSize; - ImGui.SetCursorPos(startCursor); - - return isOpen; - } - - private void DrawChangelog(IChangelogEntry log) - { - ImGui.Separator(); - - var startCursor = ImGui.GetCursorPos(); - - var iconSize = ImGuiHelpers.ScaledVector2(64, 64); - var cursorBeforeImage = ImGui.GetCursorPos(); - var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos(); - if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize)) - { - TextureWrap icon; - if (log is PluginChangelogEntry pluginLog) - { - icon = this.imageCache.DefaultIcon; - var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.Manifest.IsThirdParty, out var cachedIconTex); - if (hasIcon && cachedIconTex != null) - { - icon = cachedIconTex; - } - } - else - { - icon = this.imageCache.CorePluginIcon; - } - - ImGui.Image(icon.ImGuiHandle, iconSize); - } - else - { - ImGui.Dummy(iconSize); - } - - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.SameLine(); - var cursor = ImGui.GetCursorPos(); - ImGui.TextUnformatted(log.Title); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{log.Version}"); - - cursor.Y += ImGui.GetTextLineHeightWithSpacing(); - ImGui.SetCursorPos(cursor); - - ImGuiHelpers.SafeTextWrapped(log.Text); - - var endCursor = ImGui.GetCursorPos(); - - var sectionSize = Math.Max( - 66 * ImGuiHelpers.GlobalScale, // min size due to icons - endCursor.Y - startCursor.Y); - - startCursor.Y += sectionSize; - ImGui.SetCursorPos(startCursor); - } - - 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); - - var isOutdated = manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; - - // 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 || manifest.IsTestingExclusive) - { - 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, isOutdated, () => 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)) - { - ImGuiHelpers.SafeTextWrapped(manifest.Description); - } - - ImGuiHelpers.ScaledDummy(5); - - // Controls - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress || isOutdated; - - var versionString = useTesting - ? $"{manifest.TestingAssemblyVersion}" - : $"{manifest.AssemblyVersion}"; - - if (pluginManager.SafeMode) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_SafeMode); - } - else 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; - this.loadingIndicatorKind = LoadingIndicatorKind.Installing; - - Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting || manifest.IsTestingExclusive, 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 && manifest.AcceptsFeedback) - { - this.DrawSendFeedbackButton(manifest, false); - } - - ImGuiHelpers.ScaledDummy(5); - - if (this.DrawPluginImages(null, manifest, isThirdParty, index)) - ImGuiHelpers.ScaledDummy(5); - - ImGui.Unindent(); + this.DrawAvailablePlugin(remoteManifest, i++); } ImGui.PopID(); } + } - private void DrawAvailablePluginContextMenu(PluginManifest manifest) + private void DrawInstalledPluginList() + { + var pluginList = this.pluginListInstalled; + + if (pluginList.Count == 0) { - 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(); - } + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; } - private void DrawInstalledPlugin(LocalPlugin plugin, int index, bool showInstalled = false) + var filteredList = pluginList + .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) + .ToList(); + + if (filteredList.Count == 0) { - var configuration = Service.Get(); - var commandManager = Service.Get(); - - var testingOptIn = - configuration.PluginTestingOptIns?.FirstOrDefault(x => x.InternalName == plugin.Manifest.InternalName); - var trouble = false; - - // Name - var label = plugin.Manifest.Name; - - // Dev - if (plugin.IsDev) - { - label += Locs.PluginTitleMod_DevPlugin; - } - - // Testing - if (plugin.Manifest.Testing) - { - label += Locs.PluginTitleMod_TestingVersion; - } - - if (plugin.Manifest.IsAvailableForTesting && configuration.DoPluginTest && testingOptIn == null) - { - label += Locs.PluginTitleMod_TestingAvailable; - } - - // Freshly installed - if (showInstalled) - { - label += Locs.PluginTitleMod_Installed; - } - - // Disabled - if (plugin.IsDisabled || !plugin.CheckPolicy()) - { - label += Locs.PluginTitleMod_Disabled; - trouble = true; - } - - // Load error - if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy() - && !plugin.IsOutdated && !plugin.IsBanned && !plugin.IsOrphaned) - { - 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; - } - - // Orphaned - if (plugin.IsOrphaned) - { - label += Locs.PluginTitleMod_OrphanedError; - trouble = true; - } - - // Scheduled for deletion - if (plugin.Manifest.ScheduledForDeletion) - { - label += Locs.PluginTitleMod_ScheduledForDeletion; - } - - ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); - var hasChangelog = !plugin.Manifest.Changelog.IsNullOrEmpty(); - - if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, false, () => this.DrawInstalledPluginContextMenu(plugin, testingOptIn), index)) - { - if (!this.WasPluginSeen(plugin.Manifest.InternalName)) - configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName); - - var manifest = plugin.Manifest; - - ImGui.Indent(); - - // Name - ImGui.TextUnformatted(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 && plugin.Manifest.AcceptsFeedback && availablePluginUpdate == default; - - // 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)) - { - ImGuiHelpers.SafeTextWrapped(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) - { - ImGuiHelpers.SafeTextWrapped($"{command.Key} → {command.Value.HelpMessage}"); - } - } - } - - // Controls - this.DrawPluginControlButton(plugin, availablePluginUpdate); - this.DrawDevPluginButtons(plugin); - this.DrawDeletePluginButton(plugin); - this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl); - - if (canFeedback) - { - this.DrawSendFeedbackButton(plugin.Manifest, plugin.IsTesting); - } - - if (availablePluginUpdate != default) - this.DrawUpdateSinglePluginButton(availablePluginUpdate); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.EffectiveVersion}"); - - ImGuiHelpers.ScaledDummy(5); - - if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) - ImGuiHelpers.ScaledDummy(5); - - ImGui.Unindent(); - - if (hasChangelog) - { - if (ImGui.TreeNode(Locs.PluginBody_CurrentChangeLog(plugin.Manifest.EffectiveVersion))) - { - this.DrawInstalledPluginChangelog(plugin.Manifest); - ImGui.TreePop(); - } - } - - if (availablePluginUpdate != default && !availablePluginUpdate.UpdateManifest.Changelog.IsNullOrWhitespace()) - { - var availablePluginUpdateVersion = availablePluginUpdate.UseTesting ? availablePluginUpdate.UpdateManifest.TestingAssemblyVersion : availablePluginUpdate.UpdateManifest.AssemblyVersion; - if (ImGui.TreeNode(Locs.PluginBody_UpdateChangeLog(availablePluginUpdateVersion))) - { - this.DrawInstalledPluginChangelog(availablePluginUpdate.UpdateManifest); - ImGui.TreePop(); - } - } - } - - if (thisWasUpdated && hasChangelog) - { - this.DrawInstalledPluginChangelog(plugin.Manifest); - } - - ImGui.PopID(); + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; } - private void DrawInstalledPluginChangelog(PluginManifest manifest) + var i = 0; + foreach (var plugin in filteredList) { - ImGuiHelpers.ScaledDummy(5); + this.DrawInstalledPlugin(plugin, i++); + } + } - ImGui.PushStyleColor(ImGuiCol.ChildBg, this.changelogBgColor); - ImGui.PushStyleColor(ImGuiCol.Text, this.changelogTextColor); + private void DrawInstalledDevPluginList() + { + var pluginList = this.pluginListInstalled + .Where(plugin => plugin.IsDev) + .ToList(); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(7, 5)); + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; + } - if (ImGui.BeginChild("##changelog", new Vector2(-1, 100), true, ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.AlwaysAutoResize)) + 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.Text("Changelog:"); - ImGuiHelpers.ScaledDummy(2); - ImGuiHelpers.SafeTextWrapped(manifest.Changelog); + 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(); - - ImGui.PopStyleVar(); - ImGui.PopStyleColor(2); } + } - private void DrawInstalledPluginContextMenu(LocalPlugin plugin, PluginTestingOptIn? optIn) + private void DrawPluginCategorySelectors() + { + var colorSearchHighlight = Vector4.One; + unsafe { - var pluginManager = Service.Get(); - var configuration = Service.Get(); - - if (ImGui.BeginPopupContextItem("InstalledItemContextMenu")) + var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight); + if (colorPtr != null) { - var repoManifest = this.pluginListAvailable.FirstOrDefault(x => x.InternalName == plugin.Manifest.InternalName); - if (repoManifest?.IsTestingExclusive == true) - ImGui.BeginDisabled(); - - if (ImGui.MenuItem(Locs.PluginContext_TestingOptIn, string.Empty, optIn != null)) - { - if (optIn != null) - { - configuration.PluginTestingOptIns!.Remove(optIn); - } - else - { - configuration.PluginTestingOptIns!.Add(new PluginTestingOptIn(plugin.Manifest.InternalName)); - } - - configuration.Save(); - } - - if (repoManifest?.IsTestingExclusive == true) - ImGui.EndDisabled(); - - if (ImGui.MenuItem(Locs.PluginContext_DeletePluginConfigReload)) - { - Log.Debug($"Deleting config for {plugin.Manifest.InternalName}"); - - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.DeleteConfigurationAsync(plugin)) - .ContinueWith(task => - { - this.installStatus = OperationStatus.Idle; - - this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name)); - }); - } - - ImGui.EndPopup(); + colorSearchHighlight = *colorPtr; } } - private void DrawPluginControlButton(LocalPlugin plugin, AvailablePluginUpdate? availableUpdate) + for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++) { - var notifications = Service.Get(); - var pluginManager = 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 && !pluginManager.LoadAllApiLevels) || plugin.IsBanned; - - // Disable everything if the plugin is orphaned - disabled = disabled || plugin.IsOrphaned; - - // Disable everything if the plugin failed to load - disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed; - - // Disable everything if we're working - disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading; - - var toggleId = plugin.Manifest.InternalName; - var isLoadedAndUnloadable = plugin.State == PluginState.Loaded || - plugin.State == PluginState.DependencyResolutionFailed; - - StyleModelV1.DalamudStandard.Push(); - - if (plugin.State == PluginState.UnloadError) + var groupInfo = this.categoryManager.GroupList[groupIdx]; + var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins; + if (!canShowGroup) { - ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); + continue; } - else if (disabled) + + ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx); + if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None)) { - ImGuiComponents.DisabledToggleButton(toggleId, isLoadedAndUnloadable); - } - else - { - if (ImGuiComponents.ToggleButton(toggleId, ref isLoadedAndUnloadable)) + if (this.categoryManager.CurrentGroupIdx != groupIdx) { - if (!isLoadedAndUnloadable) + 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) { - this.enableDisableStatus = OperationStatus.InProgress; - this.loadingIndicatorKind = LoadingIndicatorKind.DisablingSingle; - - Task.Run(() => - { - if (plugin.IsDev) - { - plugin.ReloadManifest(); - } - - var unloadTask = Task.Run(() => plugin.UnloadAsync()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); - - unloadTask.Wait(); - if (!unloadTask.Result) - { - this.enableDisableStatus = OperationStatus.Complete; - return; - } - - var disableTask = Task.Run(() => plugin.Disable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); - - disableTask.Wait(); - this.enableDisableStatus = OperationStatus.Complete; - - if (!disableTask.Result) - return; - - notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); - }); + ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight); } - else + + if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize)) { - var enabler = new Task(() => - { - this.enableDisableStatus = OperationStatus.InProgress; - this.loadingIndicatorKind = LoadingIndicatorKind.EnablingSingle; + this.categoryManager.CurrentCategoryIdx = categoryIdx; + } - if (plugin.IsDev) - { - plugin.ReloadManifest(); - } - - var enableTask = Task.Run(plugin.Enable) - .ContinueWith( - this.DisplayErrorContinuation, - Locs.ErrorModal_EnableFail(plugin.Name)); - - enableTask.Wait(); - if (!enableTask.Result) - { - this.enableDisableStatus = OperationStatus.Complete; - return; - } - - var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) - .ContinueWith( - this.DisplayErrorContinuation, - Locs.ErrorModal_LoadFail(plugin.Name)); - - loadTask.Wait(); - this.enableDisableStatus = OperationStatus.Complete; - - if (!loadTask.Result) - return; - - notifications.AddNotification( - Locs.Notifications_PluginEnabled(plugin.Manifest.Name), - Locs.Notifications_PluginEnabledTitle, - NotificationType.Success); - }); - - if (availableUpdate != default && !availableUpdate.InstalledPlugin.IsDev) - { - this.ShowUpdateModal(plugin).ContinueWith(async t => - { - var shouldUpdate = t.Result; - - if (shouldUpdate) - { - await this.UpdateSinglePlugin(availableUpdate); - } - else - { - enabler.Start(); - } - }); - } - else - { - enabler.Start(); - } + if (hasSearchHighlight) + { + ImGui.PopStyleColor(); } } + + ImGui.Unindent(); + + if (groupIdx != this.categoryManager.GroupList.Length - 1) + { + ImGuiHelpers.ScaledDummy(5); + } + } + } + } + + private void DrawPluginCategoryContent() + { + var ready = this.DrawPluginListLoading() && !this.AnyOperationInProgress; + if (!this.categoryManager.IsSelectionValid || !ready) + { + return; + } + + var pm = Service.Get(); + if (pm.SafeMode) + { + ImGuiHelpers.ScaledDummy(10); + + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); + ImGui.PushFont(InterfaceManager.IconFont); + ImGuiHelpers.CenteredText(FontAwesomeIcon.ExclamationTriangle.ToIconString()); + ImGui.PopFont(); + ImGui.PopStyleColor(); + + var lines = Locs.SafeModeDisclaimer.Split('\n'); + foreach (var line in lines) + { + ImGuiHelpers.CenteredText(line); } - StyleModelV1.DalamudStandard.Pop(); + ImGuiHelpers.ScaledDummy(10); + ImGui.Separator(); + } + 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(); + } + } + + switch (groupInfo.GroupKind) + { + case 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; + } + + break; + case PluginCategoryManager.GroupKind.Installed: + this.DrawInstalledPluginList(); + break; + case PluginCategoryManager.GroupKind.Changelog: + switch (this.categoryManager.CurrentCategoryIdx) + { + case 0: + this.DrawChangelogList(true, true); + break; + + case 1: + this.DrawChangelogList(true, false); + break; + + case 2: + this.DrawChangelogList(false, true); + break; + } + + break; + default: + this.DrawAvailablePluginList(); + break; + } + + 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(); - ImGuiHelpers.ScaledDummy(15, 0); - - if (plugin.State == PluginState.Loaded) - { - // Only if the plugin isn't broken. - this.DrawOpenPluginSettingsButton(plugin); - } } - - private async Task UpdateSinglePlugin(AvailablePluginUpdate update) - { - var pluginManager = Service.Get(); - - this.installStatus = OperationStatus.InProgress; - this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingSingle; - - return await 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); - return this.DisplayErrorContinuation(task, errorMessage); - }); - } - - private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update) + else if (this.testerUpdateAvailable) { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) - { - Task.Run(() => this.UpdateSinglePlugin(update)); - } - - 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}"); - } - } + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration); - } - } + 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 void DrawSendFeedbackButton(PluginManifest manifest, bool isTesting) + this.DrawVisitRepoUrlButton("https://google.com"); + + if (this.testerImages != null) { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment)) - { - this.feedbackPlugin = manifest; - this.feedbackModalOnNextFrame = true; - this.feedbackIsTesting = isTesting; - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.FeedbackModal_Title); - } - } - - private void DrawDevPluginButtons(LocalPlugin localPlugin) - { - ImGui.SameLine(); - - 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 || plugin.State == PluginState.LoadError; - - // When policy check fails, the plugin is never loaded - var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned || plugin.IsOrphaned || !plugin.CheckPolicy()); - - if (!showButton) - return;*/ - - var pluginManager = Service.Get(); - - var devNotDeletable = plugin.IsDev && plugin.State != PluginState.Unloaded && plugin.State != PluginState.DependencyResolutionFailed; - - ImGui.SameLine(); - if (plugin.State == PluginState.Loaded || devNotDeletable) - { - ImGui.PushFont(InterfaceManager.IconFont); - ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(plugin.State == PluginState.Loaded - ? Locs.PluginButtonToolTip_DeletePluginLoaded - : Locs.PluginButtonToolTip_DeletePluginRestricted); - } - } - else - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt)) - { - try - { - if (plugin.IsDev) - { - plugin.DllFile.Delete(); - } - else - { - plugin.ScheduleDeletion(!plugin.Manifest.ScheduledForDeletion); - } - - if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed) - { - pluginManager.RemovePlugin(plugin); - } - } - catch (Exception ex) - { - Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); - - this.ShowErrorModal(Locs.ErrorModal_DeleteFail(plugin.Name)); - } - } - - if (ImGui.IsItemHovered()) - { - string tooltipMessage; - if (plugin.Manifest.ScheduledForDeletion) - { - tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduledCancel; - } - else if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed) - { - tooltipMessage = Locs.PluginButtonToolTip_DeletePlugin; - } - else - { - tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduled; - } - - ImGui.SetTooltip(tooltipMessage); - } - } - } - - 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.All(x => x == null)) - return false; + ImGuiHelpers.ScaledDummy(5); const float thumbFactor = 2.7f; @@ -2489,55 +1228,64 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller 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(); + } } } } @@ -2547,504 +1295,1755 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.PopStyleVar(); ImGui.PopStyleColor(); - return true; + ImGui.Unindent(); } - private bool IsManifestFiltered(PluginManifest manifest) + ImGuiHelpers.ScaledDummy(20); + + static void CheckImageSize(TextureWrap? image, int maxWidth, int maxHeight, bool requireSquare) { - var searchString = this.searchText.ToLowerInvariant(); - var hasSearchString = !string.IsNullOrWhiteSpace(searchString); - var oldApi = manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; - var installed = this.IsManifestInstalled(manifest).IsInstalled; - - if (oldApi && !hasSearchString && !installed) - return true; - - return hasSearchString && !( - manifest.Name.ToLowerInvariant().Contains(searchString) || - manifest.InternalName.ToLowerInvariant().Contains(searchString) || - (!manifest.Author.IsNullOrEmpty() && manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase)) || - (!manifest.Punchline.IsNullOrEmpty() && manifest.Punchline.ToLowerInvariant().Contains(searchString)) || - (manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase))); + if (image == null) + return; + if (image.Width > maxWidth || image.Height > maxHeight) + ImGui.TextColored(ImGuiColors.DalamudRed, $"Image is larger than the maximum allowed resolution ({image.Width}x{image.Height} > {maxWidth}x{maxHeight})"); + if (requireSquare && image.Width != image.Height) + ImGui.TextColored(ImGuiColors.DalamudRed, $"Image must be square! Current size: {image.Width}x{image.Height}"); } - private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(PluginManifest? manifest) + ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); + if (this.testerIcon != null) + CheckImageSize(this.testerIcon, PluginImageCache.PluginIconWidth, PluginImageCache.PluginIconHeight, true); + ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000); + if (this.testerImages?.Length > 0) + CheckImageSize(this.testerImages[0], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); + ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000); + if (this.testerImages?.Length > 1) + CheckImageSize(this.testerImages[1], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); + ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000); + if (this.testerImages?.Length > 2) + CheckImageSize(this.testerImages[2], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); + ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000); + if (this.testerImages?.Length > 3) + CheckImageSize(this.testerImages[3], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); + ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000); + if (this.testerImages?.Length > 4) + CheckImageSize(this.testerImages[4], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); + + var im = Service.Get(); + if (ImGui.Button("Load")) { - if (manifest == null) return (false, default); - - 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.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; - case PluginSortKind.NotInstalled: - this.pluginListAvailable.Sort((p1, p2) => this.pluginListInstalled.Any(x => x.Manifest.InternalName == p1.InternalName) - .CompareTo(this.pluginListInstalled.Any(x => x.Manifest.InternalName == p2.InternalName))); - this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); // Makes no sense for installed plugins - 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 Task ShowErrorModal(string message) - { - this.errorModalMessage = message; - this.errorModalDrawing = true; - this.errorModalOnNextFrame = true; - this.errorModalTaskCompletionSource = new TaskCompletionSource(); - return this.errorModalTaskCompletionSource.Task; - } - - private Task ShowUpdateModal(LocalPlugin plugin) - { - this.updateModalOnNextFrame = true; - this.updateModalPlugin = plugin; - this.updateModalTaskCompletionSource = new TaskCompletionSource(); - return this.updateModalTaskCompletionSource.Task; - } - - 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(); + + 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, bool installableOutdated, 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 pluginDisabled = plugin is { IsDisabled: true }; + + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); + var cursorBeforeImage = ImGui.GetCursorPos(); + var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos(); + if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize)) + { + var iconTex = this.imageCache.DefaultIcon; + var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); + if (hasIcon && cachedIconTex != null) + { + iconTex = cachedIconTex; + } + + if (pluginDisabled || installableOutdated) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.4f); + } + + ImGui.Image(iconTex.ImGuiHandle, iconSize); + + if (pluginDisabled || installableOutdated) + { + ImGui.PopStyleVar(); + } + + ImGui.SameLine(); + ImGui.SetCursorPos(cursorBeforeImage); + } + + var isLoaded = plugin is { IsLoaded: true }; + + if (updateAvailable) + ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); + else if (trouble && !pluginDisabled) + ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); + else if (installableOutdated) + ImGui.Image(this.imageCache.OutdatedInstallableIcon.ImGuiHandle, iconSize); + else if (pluginDisabled) + ImGui.Image(this.imageCache.DisabledIcon.ImGuiHandle, iconSize); + else if (isLoaded && isThirdParty) + ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize); + else if (isThirdParty) + ImGui.Image(this.imageCache.ThirdIcon.ImGuiHandle, iconSize); + else if (isLoaded) + ImGui.Image(this.imageCache.InstalledIcon.ImGuiHandle, iconSize); + else + ImGui.Dummy(iconSize); + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + var cursor = ImGui.GetCursorPos(); + + // Name + ImGui.TextUnformatted(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 } || installableOutdated) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(Locs.PluginBody_Outdated); + ImGui.PopStyleColor(); + } + else if (plugin is { IsBanned: true }) + { + // Banned warning + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGuiHelpers.SafeTextWrapped(plugin.BanReason.IsNullOrEmpty() + ? Locs.PluginBody_Banned + : Locs.PluginBody_BannedReason(plugin.BanReason)); + + ImGui.PopStyleColor(); + } + else if (plugin is { IsOrphaned: true }) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(Locs.PluginBody_Orphaned); + ImGui.PopStyleColor(); + } + else if (plugin != null && !plugin.CheckPolicy()) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(Locs.PluginBody_Policy); + ImGui.PopStyleColor(); + } + else if (plugin is { State: PluginState.LoadError or PluginState.DependencyResolutionFailed }) + { + // Load failed warning + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(Locs.PluginBody_LoadFailed); + ImGui.PopStyleColor(); + } + + ImGui.SetCursorPosX(cursor.X); + + // Description + if (plugin is null or { IsOutdated: false, IsBanned: false }) + { + if (!string.IsNullOrWhiteSpace(manifest.Punchline)) + { + ImGuiHelpers.SafeTextWrapped(manifest.Punchline); + } + else if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + const int punchlineLen = 200; + var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0]; + + ImGuiHelpers.SafeTextWrapped(firstLine.Length < punchlineLen + ? firstLine + : firstLine[..punchlineLen]); } } - private void UpdateCategoriesOnPluginsChange() + startCursor.Y += sectionSize; + ImGui.SetCursorPos(startCursor); + + return isOpen; + } + + private void DrawChangelog(IChangelogEntry log) + { + ImGui.Separator(); + + var startCursor = ImGui.GetCursorPos(); + + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); + var cursorBeforeImage = ImGui.GetCursorPos(); + var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos(); + if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize)) { - this.categoryManager.BuildCategories(this.pluginListAvailable); - this.UpdateCategoriesOnSearchChange(); + TextureWrap icon; + if (log is PluginChangelogEntry pluginLog) + { + icon = this.imageCache.DefaultIcon; + var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.Manifest.IsThirdParty, out var cachedIconTex); + if (hasIcon && cachedIconTex != null) + { + icon = cachedIconTex; + } + } + else + { + icon = this.imageCache.CorePluginIcon; + } + + ImGui.Image(icon.ImGuiHandle, iconSize); + } + else + { + ImGui.Dummy(iconSize); } - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")] - private static class Locs + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.SameLine(); + var cursor = ImGui.GetCursorPos(); + ImGui.TextUnformatted(log.Title); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{log.Version}"); + + cursor.Y += ImGui.GetTextLineHeightWithSpacing(); + ImGui.SetCursorPos(cursor); + + ImGuiHelpers.SafeTextWrapped(log.Text); + + var endCursor = ImGui.GetCursorPos(); + + var sectionSize = Math.Max( + 66 * ImGuiHelpers.GlobalScale, // min size due to icons + endCursor.Y - startCursor.Y); + + startCursor.Y += sectionSize; + ImGui.SetCursorPos(startCursor); + } + + 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); + + var isOutdated = manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; + + // Check for valid versions + if ((useTesting && manifest.TestingAssemblyVersion == null) || manifest.AssemblyVersion == null) { - #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_NotInstalled => Loc.Localize("InstallerNotInstalled", "Not Installed"); - - public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By"); - - #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 \"All Plugins\" tab."); - - public static string TabBody_ChangelogNone => Loc.Localize("InstallerNoChangelog", "None of your installed plugins have a changelog."); - - #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_TestingAvailable => Loc.Localize("InstallerTestingAvailable", " (available for testing)"); - - public static string PluginTitleMod_DevPlugin => Loc.Localize("InstallerDevPlugin", " (dev plugin)"); - - 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", " (automatically disabled)"); - - public static string PluginTitleMod_OrphanedError => Loc.Localize("InstallerOrphanedError", " (unknown repository)"); - - public static string PluginTitleMod_ScheduledForDeletion => Loc.Localize("InstallerScheduledForDeletion", " (scheduled for deletion)"); - - public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); - - #endregion - - #region Plugin context menu - - public static string PluginContext_TestingOptIn => Loc.Localize("InstallerTestingOptIn", "Receive plugin testing versions"); - - 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 configuration"); - - public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin configuration and 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.ToString("N0")); - - public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}").Format(author); - - public static string PluginBody_CurrentChangeLog(Version version) => Loc.Localize("InstallerCurrentChangeLog", "Changelog (v{0})").Format(version); - - public static string PluginBody_UpdateChangeLog(Version version) => Loc.Localize("InstallerUpdateChangeLog", "Available update changelog (v{0})").Format(version); - - 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_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_Orphaned => Loc.Localize("InstallerOrphanedPluginBody ", "This plugin's source repository is no longer available. You may need to reinstall it from its repository, or re-add the repository."); - - public static string PluginBody_LoadFailed => Loc.Localize("InstallerLoadFailedPluginBody ", "This plugin failed to load. Please contact the author for more information."); - - public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin was automatically disabled due to incompatibilities and is not available at the moment. Please wait for it to be updated by its author."); - - public static string PluginBody_Policy => Loc.Localize("InstallerPolicyPluginBody ", "Plugin loads for this type of plugin were manually disabled."); - - public static string PluginBody_BannedReason(string message) => - Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin was automatically disabled: {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"); - - public static string PluginButton_SafeMode => Loc.Localize("InstallerSafeModeButton", "Can't change in safe mode"); - - #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_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete right now - please restart the game."); - - public static string PluginButtonToolTip_DeletePluginScheduled => Loc.Localize("InstallerDeletePluginScheduled", "Delete plugin on next restart"); - - public static string PluginButtonToolTip_DeletePluginScheduledCancel => Loc.Localize("InstallerDeletePluginScheduledCancel", "Cancel scheduled deletion"); - - public static string PluginButtonToolTip_DeletePluginLoaded => Loc.Localize("InstallerDeletePluginLoaded", "Disable this plugin before deleting it."); - - 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); - - public static string Notifications_PluginEnabledTitle => Loc.Localize("NotificationsPluginEnabledTitle", "Plugin enabled!"); - - public static string Notifications_PluginEnabled(string name) => Loc.Localize("NotificationsPluginEnabled", "'{0}' was enabled.").Format(name); - - #endregion - - #region Footer - - public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins"); - - public static string FooterButton_UpdateSafeMode => Loc.Localize("InstallerUpdateSafeMode", "Can't update in safe mode"); - - 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 Update modal - - public static string UpdateModal_Title => Loc.Localize("UpdateQuestionModal", "Update Available"); - - public static string UpdateModal_UpdateAvailable(string name) => Loc.Localize("UpdateModalUpdateAvailable", "An update for \"{0}\" is available.\nDo you want to update it before enabling?\nUpdates will fix bugs and incompatibilities, and may add new features.").Format(name); - - public static string UpdateModal_Yes => Loc.Localize("UpdateModalYes", "Update plugin"); - - public static string UpdateModal_No => Loc.Localize("UpdateModalNo", "Just enable"); - - #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.").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_ContactAnonymous => Loc.Localize("InstallerFeedbackContactAnonymous", "Submit feedback anonymously"); - - public static string FeedbackModal_ContactAnonymousWarning => Loc.Localize("InstallerFeedbackContactAnonymousWarning", "No response will be forthcoming.\nUntick \"{0}\" and provide contact information if you need help.").Format(FeedbackModal_ContactAnonymous); - - public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); - - public static string FeedbackModal_ContactInformationHelp => Loc.Localize("InstallerFeedbackContactInfoHelp", "Discord usernames and e-mail addresses are accepted.\nIf you submit a Discord username, please join our discord server so that we can reach out to you easier."); - - public static string FeedbackModal_ContactInformationWarning => Loc.Localize("InstallerFeedbackContactInfoWarning", "Do not submit in-game character names."); - - public static string FeedbackModal_ContactInformationRequired => Loc.Localize("InstallerFeedbackContactInfoRequired", "Contact information has not been provided. If you do not want to provide contact information, tick on \"{0}\" above.").Format(FeedbackModal_ContactAnonymous); - - public static string FeedbackModal_ContactInformationDiscordButton => Loc.Localize("ContactInformationDiscordButton", "Join Goat Place Discord"); - - public static string FeedbackModal_ContactInformationDiscordUrl => Loc.Localize("ContactInformationDiscordUrl", "https://goat.place/"); - - 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 - - #region Other - - public static string SafeModeDisclaimer => Loc.Localize("SafeModeDisclaimer", "You enabled safe mode, no plugins will be loaded.\nYou may delete plugins from the \"Installed plugins\" tab.\nSimply restart your game to disable safe mode."); - - #endregion + // Without a valid version, quit + return; + } + + // Name + var label = manifest.Name; + + // Testing + if (useTesting || manifest.IsTestingExclusive) + { + 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, isOutdated, () => 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)) + { + ImGuiHelpers.SafeTextWrapped(manifest.Description); + } + + ImGuiHelpers.ScaledDummy(5); + + // Controls + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress || isOutdated; + + var versionString = useTesting + ? $"{manifest.TestingAssemblyVersion}" + : $"{manifest.AssemblyVersion}"; + + if (pluginManager.SafeMode) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_SafeMode); + } + else 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; + this.loadingIndicatorKind = LoadingIndicatorKind.Installing; + + Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting || manifest.IsTestingExclusive, 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 && manifest.AcceptsFeedback) + { + this.DrawSendFeedbackButton(manifest, false); + } + + 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 testingOptIn = + configuration.PluginTestingOptIns?.FirstOrDefault(x => x.InternalName == plugin.Manifest.InternalName); + var trouble = false; + + // Name + var label = plugin.Manifest.Name; + + // Dev + if (plugin.IsDev) + { + label += Locs.PluginTitleMod_DevPlugin; + } + + // Testing + if (plugin.Manifest.Testing) + { + label += Locs.PluginTitleMod_TestingVersion; + } + + if (plugin.Manifest.IsAvailableForTesting && configuration.DoPluginTest && testingOptIn == null) + { + label += Locs.PluginTitleMod_TestingAvailable; + } + + // Freshly installed + if (showInstalled) + { + label += Locs.PluginTitleMod_Installed; + } + + // Disabled + if (plugin.IsDisabled || !plugin.CheckPolicy()) + { + label += Locs.PluginTitleMod_Disabled; + trouble = true; + } + + // Load error + if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy() + && !plugin.IsOutdated && !plugin.IsBanned && !plugin.IsOrphaned) + { + 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; + } + + // Orphaned + if (plugin.IsOrphaned) + { + label += Locs.PluginTitleMod_OrphanedError; + trouble = true; + } + + // Scheduled for deletion + if (plugin.Manifest.ScheduledForDeletion) + { + label += Locs.PluginTitleMod_ScheduledForDeletion; + } + + ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); + var hasChangelog = !plugin.Manifest.Changelog.IsNullOrEmpty(); + + if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, false, () => this.DrawInstalledPluginContextMenu(plugin, testingOptIn), index)) + { + if (!this.WasPluginSeen(plugin.Manifest.InternalName)) + configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName); + + var manifest = plugin.Manifest; + + ImGui.Indent(); + + // Name + ImGui.TextUnformatted(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 && plugin.Manifest.AcceptsFeedback && availablePluginUpdate == default; + + // 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)) + { + ImGuiHelpers.SafeTextWrapped(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) + { + ImGuiHelpers.SafeTextWrapped($"{command.Key} → {command.Value.HelpMessage}"); + } + } + } + + // Controls + this.DrawPluginControlButton(plugin, availablePluginUpdate); + this.DrawDevPluginButtons(plugin); + this.DrawDeletePluginButton(plugin); + this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl); + + if (canFeedback) + { + this.DrawSendFeedbackButton(plugin.Manifest, plugin.IsTesting); + } + + if (availablePluginUpdate != default) + this.DrawUpdateSinglePluginButton(availablePluginUpdate); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.EffectiveVersion}"); + + ImGuiHelpers.ScaledDummy(5); + + if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) + ImGuiHelpers.ScaledDummy(5); + + ImGui.Unindent(); + + if (hasChangelog) + { + if (ImGui.TreeNode(Locs.PluginBody_CurrentChangeLog(plugin.Manifest.EffectiveVersion))) + { + this.DrawInstalledPluginChangelog(plugin.Manifest); + ImGui.TreePop(); + } + } + + if (availablePluginUpdate != default && !availablePluginUpdate.UpdateManifest.Changelog.IsNullOrWhitespace()) + { + var availablePluginUpdateVersion = availablePluginUpdate.UseTesting ? availablePluginUpdate.UpdateManifest.TestingAssemblyVersion : availablePluginUpdate.UpdateManifest.AssemblyVersion; + if (ImGui.TreeNode(Locs.PluginBody_UpdateChangeLog(availablePluginUpdateVersion))) + { + this.DrawInstalledPluginChangelog(availablePluginUpdate.UpdateManifest); + ImGui.TreePop(); + } + } + } + + if (thisWasUpdated && hasChangelog) + { + this.DrawInstalledPluginChangelog(plugin.Manifest); + } + + ImGui.PopID(); + } + + private void DrawInstalledPluginChangelog(PluginManifest manifest) + { + 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); + ImGuiHelpers.SafeTextWrapped(manifest.Changelog); + } + + ImGui.EndChild(); + + ImGui.PopStyleVar(); + ImGui.PopStyleColor(2); + } + + private void DrawInstalledPluginContextMenu(LocalPlugin plugin, PluginTestingOptIn? optIn) + { + var pluginManager = Service.Get(); + var configuration = Service.Get(); + + if (ImGui.BeginPopupContextItem("InstalledItemContextMenu")) + { + var repoManifest = this.pluginListAvailable.FirstOrDefault(x => x.InternalName == plugin.Manifest.InternalName); + if (repoManifest?.IsTestingExclusive == true) + ImGui.BeginDisabled(); + + if (ImGui.MenuItem(Locs.PluginContext_TestingOptIn, string.Empty, optIn != null)) + { + if (optIn != null) + { + configuration.PluginTestingOptIns!.Remove(optIn); + } + else + { + configuration.PluginTestingOptIns!.Add(new PluginTestingOptIn(plugin.Manifest.InternalName)); + } + + configuration.Save(); + } + + if (repoManifest?.IsTestingExclusive == true) + ImGui.EndDisabled(); + + if (ImGui.MenuItem(Locs.PluginContext_DeletePluginConfigReload)) + { + Log.Debug($"Deleting config for {plugin.Manifest.InternalName}"); + + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => pluginManager.DeleteConfigurationAsync(plugin)) + .ContinueWith(task => + { + this.installStatus = OperationStatus.Idle; + + this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name)); + }); + } + + ImGui.EndPopup(); + } + } + + private void DrawPluginControlButton(LocalPlugin plugin, AvailablePluginUpdate? availableUpdate) + { + var notifications = Service.Get(); + var pluginManager = 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 && !pluginManager.LoadAllApiLevels) || plugin.IsBanned; + + // Disable everything if the plugin is orphaned + disabled = disabled || plugin.IsOrphaned; + + // Disable everything if the plugin failed to load + disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed; + + // Disable everything if we're working + disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading; + + var toggleId = plugin.Manifest.InternalName; + var isLoadedAndUnloadable = plugin.State == PluginState.Loaded || + plugin.State == PluginState.DependencyResolutionFailed; + + StyleModelV1.DalamudStandard.Push(); + + if (plugin.State == PluginState.UnloadError) + { + ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); + } + else if (disabled) + { + ImGuiComponents.DisabledToggleButton(toggleId, isLoadedAndUnloadable); + } + else + { + if (ImGuiComponents.ToggleButton(toggleId, ref isLoadedAndUnloadable)) + { + if (!isLoadedAndUnloadable) + { + this.enableDisableStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.DisablingSingle; + + Task.Run(() => + { + if (plugin.IsDev) + { + plugin.ReloadManifest(); + } + + var unloadTask = Task.Run(() => plugin.UnloadAsync()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); + + unloadTask.Wait(); + if (!unloadTask.Result) + { + this.enableDisableStatus = OperationStatus.Complete; + return; + } + + var disableTask = Task.Run(() => plugin.Disable()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); + + disableTask.Wait(); + this.enableDisableStatus = OperationStatus.Complete; + + if (!disableTask.Result) + return; + + notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); + }); + } + else + { + var enabler = new Task(() => + { + this.enableDisableStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.EnablingSingle; + + if (plugin.IsDev) + { + plugin.ReloadManifest(); + } + + var enableTask = Task.Run(plugin.Enable) + .ContinueWith( + this.DisplayErrorContinuation, + Locs.ErrorModal_EnableFail(plugin.Name)); + + enableTask.Wait(); + if (!enableTask.Result) + { + this.enableDisableStatus = OperationStatus.Complete; + return; + } + + var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) + .ContinueWith( + this.DisplayErrorContinuation, + Locs.ErrorModal_LoadFail(plugin.Name)); + + loadTask.Wait(); + this.enableDisableStatus = OperationStatus.Complete; + + if (!loadTask.Result) + return; + + notifications.AddNotification( + Locs.Notifications_PluginEnabled(plugin.Manifest.Name), + Locs.Notifications_PluginEnabledTitle, + NotificationType.Success); + }); + + if (availableUpdate != default && !availableUpdate.InstalledPlugin.IsDev) + { + this.ShowUpdateModal(plugin).ContinueWith(async t => + { + var shouldUpdate = t.Result; + + if (shouldUpdate) + { + await this.UpdateSinglePlugin(availableUpdate); + } + else + { + enabler.Start(); + } + }); + } + else + { + enabler.Start(); + } + } + } + } + + StyleModelV1.DalamudStandard.Pop(); + + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(15, 0); + + if (plugin.State == PluginState.Loaded) + { + // Only if the plugin isn't broken. + this.DrawOpenPluginSettingsButton(plugin); + } + } + + private async Task UpdateSinglePlugin(AvailablePluginUpdate update) + { + var pluginManager = Service.Get(); + + this.installStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingSingle; + + return await 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); + return this.DisplayErrorContinuation(task, errorMessage); + }); + } + + private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update) + { + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) + { + Task.Run(() => this.UpdateSinglePlugin(update)); + } + + 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, bool isTesting) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment)) + { + this.feedbackPlugin = manifest; + this.feedbackModalOnNextFrame = true; + this.feedbackIsTesting = isTesting; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.FeedbackModal_Title); + } + } + + private void DrawDevPluginButtons(LocalPlugin localPlugin) + { + ImGui.SameLine(); + + 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 || plugin.State == PluginState.LoadError; + + // When policy check fails, the plugin is never loaded + var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned || plugin.IsOrphaned || !plugin.CheckPolicy()); + + if (!showButton) + return;*/ + + var pluginManager = Service.Get(); + + var devNotDeletable = plugin.IsDev && plugin.State != PluginState.Unloaded && plugin.State != PluginState.DependencyResolutionFailed; + + ImGui.SameLine(); + if (plugin.State == PluginState.Loaded || devNotDeletable) + { + ImGui.PushFont(InterfaceManager.IconFont); + ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(plugin.State == PluginState.Loaded + ? Locs.PluginButtonToolTip_DeletePluginLoaded + : Locs.PluginButtonToolTip_DeletePluginRestricted); + } + } + else + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt)) + { + try + { + if (plugin.IsDev) + { + plugin.DllFile.Delete(); + } + else + { + plugin.ScheduleDeletion(!plugin.Manifest.ScheduledForDeletion); + } + + if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed) + { + pluginManager.RemovePlugin(plugin); + } + } + catch (Exception ex) + { + Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); + + this.ShowErrorModal(Locs.ErrorModal_DeleteFail(plugin.Name)); + } + } + + if (ImGui.IsItemHovered()) + { + string tooltipMessage; + if (plugin.Manifest.ScheduledForDeletion) + { + tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduledCancel; + } + else if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed) + { + tooltipMessage = Locs.PluginButtonToolTip_DeletePlugin; + } + else + { + tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduled; + } + + ImGui.SetTooltip(tooltipMessage); + } + } + } + + 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.All(x => x == null)) + 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); + var oldApi = manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; + var installed = this.IsManifestInstalled(manifest).IsInstalled; + + if (oldApi && !hasSearchString && !installed) + return true; + + return hasSearchString && !( + manifest.Name.ToLowerInvariant().Contains(searchString) || + manifest.InternalName.ToLowerInvariant().Contains(searchString) || + (!manifest.Author.IsNullOrEmpty() && manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase)) || + (!manifest.Punchline.IsNullOrEmpty() && manifest.Punchline.ToLowerInvariant().Contains(searchString)) || + (manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase))); + } + + private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(PluginManifest? manifest) + { + if (manifest == null) return (false, default); + + 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.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; + case PluginSortKind.NotInstalled: + this.pluginListAvailable.Sort((p1, p2) => this.pluginListInstalled.Any(x => x.Manifest.InternalName == p1.InternalName) + .CompareTo(this.pluginListInstalled.Any(x => x.Manifest.InternalName == p2.InternalName))); + this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); // Makes no sense for installed plugins + 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 Task ShowErrorModal(string message) + { + this.errorModalMessage = message; + this.errorModalDrawing = true; + this.errorModalOnNextFrame = true; + this.errorModalTaskCompletionSource = new TaskCompletionSource(); + return this.errorModalTaskCompletionSource.Task; + } + + private Task ShowUpdateModal(LocalPlugin plugin) + { + this.updateModalOnNextFrame = true; + this.updateModalPlugin = plugin; + this.updateModalTaskCompletionSource = new TaskCompletionSource(); + return this.updateModalTaskCompletionSource.Task; + } + + 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_NotInstalled => Loc.Localize("InstallerNotInstalled", "Not Installed"); + + public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By"); + + #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 \"All Plugins\" tab."); + + public static string TabBody_ChangelogNone => Loc.Localize("InstallerNoChangelog", "None of your installed plugins have a changelog."); + + #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_TestingAvailable => Loc.Localize("InstallerTestingAvailable", " (available for testing)"); + + public static string PluginTitleMod_DevPlugin => Loc.Localize("InstallerDevPlugin", " (dev plugin)"); + + 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", " (automatically disabled)"); + + public static string PluginTitleMod_OrphanedError => Loc.Localize("InstallerOrphanedError", " (unknown repository)"); + + public static string PluginTitleMod_ScheduledForDeletion => Loc.Localize("InstallerScheduledForDeletion", " (scheduled for deletion)"); + + public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); + + #endregion + + #region Plugin context menu + + public static string PluginContext_TestingOptIn => Loc.Localize("InstallerTestingOptIn", "Receive plugin testing versions"); + + 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 configuration"); + + public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin configuration and 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.ToString("N0")); + + public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}").Format(author); + + public static string PluginBody_CurrentChangeLog(Version version) => Loc.Localize("InstallerCurrentChangeLog", "Changelog (v{0})").Format(version); + + public static string PluginBody_UpdateChangeLog(Version version) => Loc.Localize("InstallerUpdateChangeLog", "Available update changelog (v{0})").Format(version); + + 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_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_Orphaned => Loc.Localize("InstallerOrphanedPluginBody ", "This plugin's source repository is no longer available. You may need to reinstall it from its repository, or re-add the repository."); + + public static string PluginBody_LoadFailed => Loc.Localize("InstallerLoadFailedPluginBody ", "This plugin failed to load. Please contact the author for more information."); + + public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin was automatically disabled due to incompatibilities and is not available at the moment. Please wait for it to be updated by its author."); + + public static string PluginBody_Policy => Loc.Localize("InstallerPolicyPluginBody ", "Plugin loads for this type of plugin were manually disabled."); + + public static string PluginBody_BannedReason(string message) => + Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin was automatically disabled: {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"); + + public static string PluginButton_SafeMode => Loc.Localize("InstallerSafeModeButton", "Can't change in safe mode"); + + #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_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete right now - please restart the game."); + + public static string PluginButtonToolTip_DeletePluginScheduled => Loc.Localize("InstallerDeletePluginScheduled", "Delete plugin on next restart"); + + public static string PluginButtonToolTip_DeletePluginScheduledCancel => Loc.Localize("InstallerDeletePluginScheduledCancel", "Cancel scheduled deletion"); + + public static string PluginButtonToolTip_DeletePluginLoaded => Loc.Localize("InstallerDeletePluginLoaded", "Disable this plugin before deleting it."); + + 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); + + public static string Notifications_PluginEnabledTitle => Loc.Localize("NotificationsPluginEnabledTitle", "Plugin enabled!"); + + public static string Notifications_PluginEnabled(string name) => Loc.Localize("NotificationsPluginEnabled", "'{0}' was enabled.").Format(name); + + #endregion + + #region Footer + + public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins"); + + public static string FooterButton_UpdateSafeMode => Loc.Localize("InstallerUpdateSafeMode", "Can't update in safe mode"); + + 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 Update modal + + public static string UpdateModal_Title => Loc.Localize("UpdateQuestionModal", "Update Available"); + + public static string UpdateModal_UpdateAvailable(string name) => Loc.Localize("UpdateModalUpdateAvailable", "An update for \"{0}\" is available.\nDo you want to update it before enabling?\nUpdates will fix bugs and incompatibilities, and may add new features.").Format(name); + + public static string UpdateModal_Yes => Loc.Localize("UpdateModalYes", "Update plugin"); + + public static string UpdateModal_No => Loc.Localize("UpdateModalNo", "Just enable"); + + #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.").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_ContactAnonymous => Loc.Localize("InstallerFeedbackContactAnonymous", "Submit feedback anonymously"); + + public static string FeedbackModal_ContactAnonymousWarning => Loc.Localize("InstallerFeedbackContactAnonymousWarning", "No response will be forthcoming.\nUntick \"{0}\" and provide contact information if you need help.").Format(FeedbackModal_ContactAnonymous); + + public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); + + public static string FeedbackModal_ContactInformationHelp => Loc.Localize("InstallerFeedbackContactInfoHelp", "Discord usernames and e-mail addresses are accepted.\nIf you submit a Discord username, please join our discord server so that we can reach out to you easier."); + + public static string FeedbackModal_ContactInformationWarning => Loc.Localize("InstallerFeedbackContactInfoWarning", "Do not submit in-game character names."); + + public static string FeedbackModal_ContactInformationRequired => Loc.Localize("InstallerFeedbackContactInfoRequired", "Contact information has not been provided. If you do not want to provide contact information, tick on \"{0}\" above.").Format(FeedbackModal_ContactAnonymous); + + public static string FeedbackModal_ContactInformationDiscordButton => Loc.Localize("ContactInformationDiscordButton", "Join Goat Place Discord"); + + public static string FeedbackModal_ContactInformationDiscordUrl => Loc.Localize("ContactInformationDiscordUrl", "https://goat.place/"); + + 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 + + #region Other + + public static string SafeModeDisclaimer => Loc.Localize("SafeModeDisclaimer", "You enabled safe mode, no plugins will be loaded.\nYou may delete plugins from the \"Installed plugins\" tab.\nSimply restart your game to disable safe mode."); + + #endregion + } } diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index 4ae3655b2..5ea7f3db6 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -12,276 +12,113 @@ 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") + this.Size = new Vector2(810, 520); + this.SizeCondition = ImGuiCond.FirstUseEver; + } + + /// + public override void Draw() + { + var pluginManager = Service.Get(); + + ImGui.BeginTabBar("Stat Tabs"); + + if (ImGui.BeginTabItem("Draw times")) { - this.RespectCloseHotkey = false; + var doStats = UiBuilder.DoStats; - this.Size = new Vector2(810, 520); - this.SizeCondition = ImGuiCond.FirstUseEver; - } - - /// - 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) - { - if (plugin.DalamudInterface != null) - { - plugin.DalamudInterface.UiBuilder.LastDrawTime = -1; - plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1; - plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear(); - } - } - } - - if (ImGui.BeginTable( - "##PluginStatsDrawTimes", - 4, - ImGuiTableFlags.RowBg - | ImGuiTableFlags.SizingStretchProp - | ImGuiTableFlags.Sortable - | ImGuiTableFlags.Resizable - | ImGuiTableFlags.ScrollY - | ImGuiTableFlags.Reorderable - | ImGuiTableFlags.Hideable)) - { - ImGui.TableSetupScrollFreeze(0, 1); - ImGui.TableSetupColumn("Plugin"); - ImGui.TableSetupColumn("Last", ImGuiTableColumnFlags.NoSort); // Changes too fast to sort - ImGui.TableSetupColumn("Longest"); - ImGui.TableSetupColumn("Average"); - ImGui.TableHeadersRow(); - - var loadedPlugins = pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded); - - var sortSpecs = ImGui.TableGetSortSpecs(); - loadedPlugins = sortSpecs.Specs.ColumnIndex switch - { - 0 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending - ? loadedPlugins.OrderBy(plugin => plugin.Name) - : loadedPlugins.OrderByDescending(plugin => plugin.Name), - 2 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending - ? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime) - : loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime), - 3 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending - ? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.Average()) - : loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.Average()), - _ => loadedPlugins, - }; - - foreach (var plugin in loadedPlugins) - { - ImGui.TableNextRow(); - - ImGui.TableNextColumn(); - ImGui.Text(plugin.Manifest.Name); - - if (plugin.DalamudInterface != null) - { - ImGui.TableNextColumn(); - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); - - ImGui.TableNextColumn(); - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); - - ImGui.TableNextColumn(); - ImGui.Text(plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0 - ? $"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms" - : "-"); - } - } - - ImGui.EndTable(); - } - } - - 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; - } - - if (doStats) - { - ImGui.SameLine(); - if (ImGui.Button("Reset")) + foreach (var plugin in pluginManager.InstalledPlugins) { - Framework.StatsHistory.Clear(); - } - - if (ImGui.BeginTable( - "##PluginStatsFrameworkTimes", - 4, - ImGuiTableFlags.RowBg - | ImGuiTableFlags.SizingStretchProp - | ImGuiTableFlags.Sortable - | ImGuiTableFlags.Resizable - | ImGuiTableFlags.ScrollY - | ImGuiTableFlags.Reorderable - | ImGuiTableFlags.Hideable)) - { - ImGui.TableSetupScrollFreeze(0, 1); - ImGui.TableSetupColumn("Method", ImGuiTableColumnFlags.None, 250); - ImGui.TableSetupColumn("Last", ImGuiTableColumnFlags.NoSort, 50); // Changes too fast to sort - ImGui.TableSetupColumn("Longest", ImGuiTableColumnFlags.None, 50); - ImGui.TableSetupColumn("Average", ImGuiTableColumnFlags.None, 50); - ImGui.TableHeadersRow(); - - var statsHistory = Framework.StatsHistory.ToArray(); - - var sortSpecs = ImGui.TableGetSortSpecs(); - statsHistory = sortSpecs.Specs.ColumnIndex switch + if (plugin.DalamudInterface != null) { - 0 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending - ? statsHistory.OrderBy(handler => handler.Key).ToArray() - : statsHistory.OrderByDescending(handler => handler.Key).ToArray(), - 2 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending - ? statsHistory.OrderBy(handler => handler.Value.Max()).ToArray() - : statsHistory.OrderByDescending(handler => handler.Value.Max()).ToArray(), - 3 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending - ? statsHistory.OrderBy(handler => handler.Value.Average()).ToArray() - : statsHistory.OrderByDescending(handler => handler.Value.Average()).ToArray(), - _ => statsHistory, - }; - - foreach (var handlerHistory in statsHistory) - { - ImGui.TableNextRow(); - - ImGui.TableNextColumn(); - ImGui.Text($"{handlerHistory.Key}"); - - ImGui.TableNextColumn(); - ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); - - ImGui.TableNextColumn(); - ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); - - ImGui.TableNextColumn(); - ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); + plugin.DalamudInterface.UiBuilder.LastDrawTime = -1; + plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1; + plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear(); } - - ImGui.EndTable(); } } - ImGui.EndTabItem(); - } - - var toRemove = new List(); - - if (ImGui.BeginTabItem("Hooks")) - { - ImGui.Checkbox("Show Dalamud Hooks", ref this.showDalamudHooks); - if (ImGui.BeginTable( - "##PluginStatsHooks", - 4, - ImGuiTableFlags.RowBg - | ImGuiTableFlags.SizingStretchProp - | ImGuiTableFlags.Resizable - | ImGuiTableFlags.ScrollY - | ImGuiTableFlags.Reorderable - | ImGuiTableFlags.Hideable)) + "##PluginStatsDrawTimes", + 4, + ImGuiTableFlags.RowBg + | ImGuiTableFlags.SizingStretchProp + | ImGuiTableFlags.Sortable + | ImGuiTableFlags.Resizable + | ImGuiTableFlags.ScrollY + | ImGuiTableFlags.Reorderable + | ImGuiTableFlags.Hideable)) { ImGui.TableSetupScrollFreeze(0, 1); - ImGui.TableSetupColumn("Detour Method", ImGuiTableColumnFlags.None, 250); - ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.None, 100); - ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.None, 40); - ImGui.TableSetupColumn("Backend", ImGuiTableColumnFlags.None, 40); + ImGui.TableSetupColumn("Plugin"); + ImGui.TableSetupColumn("Last", ImGuiTableColumnFlags.NoSort); // Changes too fast to sort + ImGui.TableSetupColumn("Longest"); + ImGui.TableSetupColumn("Average"); ImGui.TableHeadersRow(); - foreach (var (guid, trackedHook) in HookManager.TrackedHooks) + var loadedPlugins = pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded); + + var sortSpecs = ImGui.TableGetSortSpecs(); + loadedPlugins = sortSpecs.Specs.ColumnIndex switch { - try + 0 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending + ? loadedPlugins.OrderBy(plugin => plugin.Name) + : loadedPlugins.OrderByDescending(plugin => plugin.Name), + 2 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending + ? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime) + : loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime), + 3 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending + ? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.Average()) + : loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.Average()), + _ => loadedPlugins, + }; + + foreach (var plugin in loadedPlugins) + { + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + ImGui.Text(plugin.Manifest.Name); + + if (plugin.DalamudInterface != null) { - if (trackedHook.Hook.IsDisposed) - toRemove.Add(guid); - - if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) - continue; - - ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); ImGui.TableNextColumn(); - - ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); - ImGui.TextDisabled(trackedHook.Assembly.FullName); - ImGui.TableNextColumn(); - if (!trackedHook.Hook.IsDisposed) - { - if (ImGui.Selectable($"{trackedHook.Hook.Address.ToInt64():X}")) - { - ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); - Service.Get().AddNotification($"{trackedHook.Hook.Address.ToInt64():X}", "Copied to clipboard", NotificationType.Success); - } - - var processMemoryOffset = trackedHook.InProcessMemory; - if (processMemoryOffset.HasValue) - { - if (ImGui.Selectable($"ffxiv_dx11.exe+{processMemoryOffset:X}")) - { - ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); - Service.Get().AddNotification($"ffxiv_dx11.exe+{processMemoryOffset:X}", "Copied to clipboard", NotificationType.Success); - } - } - } + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); ImGui.TableNextColumn(); - - if (trackedHook.Hook.IsDisposed) - { - ImGui.Text("Disposed"); - } - else - { - ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); - } - - ImGui.TableNextColumn(); - - ImGui.Text(trackedHook.Hook.BackendName); - } - catch (Exception ex) - { - ImGui.Text(ex.Message); + ImGui.Text(plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0 + ? $"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms" + : "-"); } } @@ -289,15 +126,177 @@ namespace Dalamud.Interface.Internal.Windows } } - if (ImGui.IsWindowAppearing()) + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Framework times")) + { + var doStats = Framework.StatsEnabled; + + if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) { - foreach (var guid in toRemove) + Framework.StatsEnabled = doStats; + } + + if (doStats) + { + ImGui.SameLine(); + if (ImGui.Button("Reset")) { - HookManager.TrackedHooks.TryRemove(guid, out _); + Framework.StatsHistory.Clear(); + } + + if (ImGui.BeginTable( + "##PluginStatsFrameworkTimes", + 4, + ImGuiTableFlags.RowBg + | ImGuiTableFlags.SizingStretchProp + | ImGuiTableFlags.Sortable + | ImGuiTableFlags.Resizable + | ImGuiTableFlags.ScrollY + | ImGuiTableFlags.Reorderable + | ImGuiTableFlags.Hideable)) + { + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("Method", ImGuiTableColumnFlags.None, 250); + ImGui.TableSetupColumn("Last", ImGuiTableColumnFlags.NoSort, 50); // Changes too fast to sort + ImGui.TableSetupColumn("Longest", ImGuiTableColumnFlags.None, 50); + ImGui.TableSetupColumn("Average", ImGuiTableColumnFlags.None, 50); + ImGui.TableHeadersRow(); + + var statsHistory = Framework.StatsHistory.ToArray(); + + var sortSpecs = ImGui.TableGetSortSpecs(); + statsHistory = sortSpecs.Specs.ColumnIndex switch + { + 0 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending + ? statsHistory.OrderBy(handler => handler.Key).ToArray() + : statsHistory.OrderByDescending(handler => handler.Key).ToArray(), + 2 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending + ? statsHistory.OrderBy(handler => handler.Value.Max()).ToArray() + : statsHistory.OrderByDescending(handler => handler.Value.Max()).ToArray(), + 3 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending + ? statsHistory.OrderBy(handler => handler.Value.Average()).ToArray() + : statsHistory.OrderByDescending(handler => handler.Value.Average()).ToArray(), + _ => statsHistory, + }; + + foreach (var handlerHistory in statsHistory) + { + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + ImGui.Text($"{handlerHistory.Key}"); + + ImGui.TableNextColumn(); + ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); + + ImGui.TableNextColumn(); + ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); + + ImGui.TableNextColumn(); + ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); + } + + ImGui.EndTable(); } } - ImGui.EndTabBar(); + ImGui.EndTabItem(); } + + var toRemove = new List(); + + if (ImGui.BeginTabItem("Hooks")) + { + ImGui.Checkbox("Show Dalamud Hooks", ref this.showDalamudHooks); + + if (ImGui.BeginTable( + "##PluginStatsHooks", + 4, + ImGuiTableFlags.RowBg + | ImGuiTableFlags.SizingStretchProp + | ImGuiTableFlags.Resizable + | ImGuiTableFlags.ScrollY + | ImGuiTableFlags.Reorderable + | ImGuiTableFlags.Hideable)) + { + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("Detour Method", ImGuiTableColumnFlags.None, 250); + ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.None, 100); + ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.None, 40); + ImGui.TableSetupColumn("Backend", ImGuiTableColumnFlags.None, 40); + ImGui.TableHeadersRow(); + + foreach (var (guid, trackedHook) in HookManager.TrackedHooks) + { + try + { + if (trackedHook.Hook.IsDisposed) + toRemove.Add(guid); + + if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) + continue; + + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + + ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); + ImGui.TextDisabled(trackedHook.Assembly.FullName); + ImGui.TableNextColumn(); + if (!trackedHook.Hook.IsDisposed) + { + if (ImGui.Selectable($"{trackedHook.Hook.Address.ToInt64():X}")) + { + ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); + Service.Get().AddNotification($"{trackedHook.Hook.Address.ToInt64():X}", "Copied to clipboard", NotificationType.Success); + } + + var processMemoryOffset = trackedHook.InProcessMemory; + if (processMemoryOffset.HasValue) + { + if (ImGui.Selectable($"ffxiv_dx11.exe+{processMemoryOffset:X}")) + { + ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); + Service.Get().AddNotification($"ffxiv_dx11.exe+{processMemoryOffset:X}", "Copied to clipboard", NotificationType.Success); + } + } + } + + ImGui.TableNextColumn(); + + if (trackedHook.Hook.IsDisposed) + { + ImGui.Text("Disposed"); + } + else + { + ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); + } + + ImGui.TableNextColumn(); + + ImGui.Text(trackedHook.Hook.BackendName); + } + catch (Exception ex) + { + ImGui.Text(ex.Message); + } + } + + ImGui.EndTable(); + } + } + + if (ImGui.IsWindowAppearing()) + { + foreach (var guid in toRemove) + { + HookManager.TrackedHooks.TryRemove(guid, out _); + } + } + + 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/AetheryteListAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs index 4035f860b..6a4519eab 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs @@ -2,52 +2,51 @@ using Dalamud.Game.ClientState.Aetherytes; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the Aetheryte List. +/// +internal class AetheryteListAgingStep : IAgingStep { - /// - /// Test setup for the Aetheryte List. - /// - internal class AetheryteListAgingStep : IAgingStep + private int index = 0; + + /// + public string Name => "Test AetheryteList"; + + /// + public SelfTestStepResult RunStep() { - private int index = 0; + var list = Service.Get(); - /// - public string Name => "Test AetheryteList"; + ImGui.Text("Checking aetheryte list..."); - /// - public SelfTestStepResult RunStep() + if (this.index == list.Length - 1) { - var list = Service.Get(); + return SelfTestStepResult.Pass; + } - ImGui.Text("Checking aetheryte list..."); - - if (this.index == list.Length - 1) - { - return SelfTestStepResult.Pass; - } - - var aetheryte = list[this.index]; - this.index++; - - if (aetheryte == null) - { - return SelfTestStepResult.Waiting; - } - - if (aetheryte.AetheryteId == 0) - { - return SelfTestStepResult.Fail; - } - - Util.ShowObject(aetheryte); + var aetheryte = list[this.index]; + this.index++; + if (aetheryte == null) + { return SelfTestStepResult.Waiting; } - /// - public void CleanUp() + if (aetheryte.AetheryteId == 0) { - // ignored + return SelfTestStepResult.Fail; } + + Util.ShowObject(aetheryte); + + 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/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs index 1931be5dc..570e362ef 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs @@ -6,247 +6,246 @@ using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Serilog;*/ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Tests for context menu. +/// +internal class ContextMenuAgingStep : IAgingStep { - /// - /// Tests for context menu. - /// - internal class ContextMenuAgingStep : IAgingStep + /* + private SubStep currentSubStep; + + private uint clickedItemId; + private bool clickedItemHq; + private uint clickedItemCount; + + private string? clickedPlayerName; + private ushort? clickedPlayerWorld; + private ulong? clickedPlayerCid; + private uint? clickedPlayerId; + + private bool multipleTriggerOne; + private bool multipleTriggerTwo; + + private enum SubStep + { + Start, + TestItem, + TestGameObject, + TestSubMenu, + TestMultiple, + Finish, + } + */ + + /// + public string Name => "Test Context Menu"; + + /// + public SelfTestStepResult RunStep() { /* - private SubStep currentSubStep; + var contextMenu = Service.Get(); + var dataMgr = Service.Get(); - private uint clickedItemId; - private bool clickedItemHq; - private uint clickedItemCount; + ImGui.Text(this.currentSubStep.ToString()); - private string? clickedPlayerName; - private ushort? clickedPlayerWorld; - private ulong? clickedPlayerCid; - private uint? clickedPlayerId; - - private bool multipleTriggerOne; - private bool multipleTriggerTwo; - - private enum SubStep + switch (this.currentSubStep) { - Start, - TestItem, - TestGameObject, - TestSubMenu, - TestMultiple, - Finish, - } - */ + case SubStep.Start: + contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; + this.currentSubStep++; + break; + case SubStep.TestItem: + if (this.clickedItemId != 0) + { + var item = dataMgr.GetExcelSheet()!.GetRow(this.clickedItemId); + ImGui.Text($"Did you click \"{item!.Name.RawString}\", hq:{this.clickedItemHq}, count:{this.clickedItemCount}?"); - /// - public string Name => "Test Context Menu"; - - /// - public SelfTestStepResult RunStep() - { - /* - var contextMenu = Service.Get(); - var dataMgr = Service.Get(); - - ImGui.Text(this.currentSubStep.ToString()); - - switch (this.currentSubStep) - { - case SubStep.Start: - contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; - this.currentSubStep++; - break; - case SubStep.TestItem: - if (this.clickedItemId != 0) - { - var item = dataMgr.GetExcelSheet()!.GetRow(this.clickedItemId); - ImGui.Text($"Did you click \"{item!.Name.RawString}\", hq:{this.clickedItemHq}, count:{this.clickedItemCount}?"); - - if (ImGui.Button("Yes")) - this.currentSubStep++; - - ImGui.SameLine(); - - if (ImGui.Button("No")) - return SelfTestStepResult.Fail; - } - else - { - ImGui.Text("Right-click an item."); - - if (ImGui.Button("Skip")) - this.currentSubStep++; - } - - break; - - case SubStep.TestGameObject: - if (!this.clickedPlayerName.IsNullOrEmpty()) - { - ImGui.Text($"Did you click \"{this.clickedPlayerName}\", world:{this.clickedPlayerWorld}, cid:{this.clickedPlayerCid}, id:{this.clickedPlayerId}?"); - - if (ImGui.Button("Yes")) - this.currentSubStep++; - - ImGui.SameLine(); - - if (ImGui.Button("No")) - return SelfTestStepResult.Fail; - } - else - { - ImGui.Text("Right-click a character."); - - if (ImGui.Button("Skip")) - this.currentSubStep++; - } - - break; - case SubStep.TestSubMenu: - if (this.multipleTriggerOne && this.multipleTriggerTwo) - { + if (ImGui.Button("Yes")) this.currentSubStep++; - this.multipleTriggerOne = this.multipleTriggerTwo = false; - } - else - { - ImGui.Text("Right-click a character and select both options in the submenu."); - if (ImGui.Button("Skip")) - this.currentSubStep++; - } + ImGui.SameLine(); - break; + if (ImGui.Button("No")) + return SelfTestStepResult.Fail; + } + else + { + ImGui.Text("Right-click an item."); - case SubStep.TestMultiple: - if (this.multipleTriggerOne && this.multipleTriggerTwo) - { - this.currentSubStep = SubStep.Finish; - return SelfTestStepResult.Pass; - } - - ImGui.Text("Select both options on any context menu."); if (ImGui.Button("Skip")) this.currentSubStep++; - break; - default: - throw new ArgumentOutOfRangeException(); - } + } - return SelfTestStepResult.Waiting; - */ + break; - return SelfTestStepResult.Pass; + case SubStep.TestGameObject: + if (!this.clickedPlayerName.IsNullOrEmpty()) + { + ImGui.Text($"Did you click \"{this.clickedPlayerName}\", world:{this.clickedPlayerWorld}, cid:{this.clickedPlayerCid}, id:{this.clickedPlayerId}?"); + + if (ImGui.Button("Yes")) + this.currentSubStep++; + + ImGui.SameLine(); + + if (ImGui.Button("No")) + return SelfTestStepResult.Fail; + } + else + { + ImGui.Text("Right-click a character."); + + if (ImGui.Button("Skip")) + this.currentSubStep++; + } + + break; + case SubStep.TestSubMenu: + if (this.multipleTriggerOne && this.multipleTriggerTwo) + { + this.currentSubStep++; + this.multipleTriggerOne = this.multipleTriggerTwo = false; + } + else + { + ImGui.Text("Right-click a character and select both options in the submenu."); + + if (ImGui.Button("Skip")) + this.currentSubStep++; + } + + break; + + case SubStep.TestMultiple: + if (this.multipleTriggerOne && this.multipleTriggerTwo) + { + this.currentSubStep = SubStep.Finish; + return SelfTestStepResult.Pass; + } + + ImGui.Text("Select both options on any context menu."); + if (ImGui.Button("Skip")) + this.currentSubStep++; + break; + default: + throw new ArgumentOutOfRangeException(); } - /// - public void CleanUp() - { - /* - var contextMenu = Service.Get(); - contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; + return SelfTestStepResult.Waiting; + */ - this.currentSubStep = SubStep.Start; - this.clickedItemId = 0; - this.clickedPlayerName = null; - this.multipleTriggerOne = this.multipleTriggerTwo = false; - */ - } + return SelfTestStepResult.Pass; + } + /// + public void CleanUp() + { /* - private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args) + var contextMenu = Service.Get(); + contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; + + this.currentSubStep = SubStep.Start; + this.clickedItemId = 0; + this.clickedPlayerName = null; + this.multipleTriggerOne = this.multipleTriggerTwo = false; + */ + } + + /* + private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args) + { + Log.Information("Got context menu with parent addon: {ParentAddonName}, title:{Title}, itemcnt:{ItemCount}", args.ParentAddonName, args.Title, args.Items.Count); + if (args.GameObjectContext != null) { - Log.Information("Got context menu with parent addon: {ParentAddonName}, title:{Title}, itemcnt:{ItemCount}", args.ParentAddonName, args.Title, args.Items.Count); - if (args.GameObjectContext != null) - { - Log.Information(" => GameObject:{GameObjectName} world:{World} cid:{Cid} id:{Id}", args.GameObjectContext.Name, args.GameObjectContext.WorldId, args.GameObjectContext.ContentId, args.GameObjectContext.Id); - } + Log.Information(" => GameObject:{GameObjectName} world:{World} cid:{Cid} id:{Id}", args.GameObjectContext.Name, args.GameObjectContext.WorldId, args.GameObjectContext.ContentId, args.GameObjectContext.Id); + } - if (args.InventoryItemContext != null) - { - Log.Information(" => Inventory:{ItemId} hq:{Hq} count:{Count}", args.InventoryItemContext.Id, args.InventoryItemContext.IsHighQuality, args.InventoryItemContext.Count); - } + if (args.InventoryItemContext != null) + { + Log.Information(" => Inventory:{ItemId} hq:{Hq} count:{Count}", args.InventoryItemContext.Id, args.InventoryItemContext.IsHighQuality, args.InventoryItemContext.Count); + } - switch (this.currentSubStep) - { - case SubStep.TestSubMenu: - args.AddCustomSubMenu("Aging Submenu", openedArgs => - { - openedArgs.AddCustomItem("Submenu Item 1", _ => - { - this.multipleTriggerOne = true; - }); - - openedArgs.AddCustomItem("Submenu Item 2", _ => - { - this.multipleTriggerTwo = true; - }); - }); - - return; - case SubStep.TestMultiple: - args.AddCustomItem("Aging Item 1", _ => + switch (this.currentSubStep) + { + case SubStep.TestSubMenu: + args.AddCustomSubMenu("Aging Submenu", openedArgs => + { + openedArgs.AddCustomItem("Submenu Item 1", _ => { this.multipleTriggerOne = true; }); - args.AddCustomItem("Aging Item 2", _ => + openedArgs.AddCustomItem("Submenu Item 2", _ => { this.multipleTriggerTwo = true; }); + }); - return; - case SubStep.Finish: - return; + return; + case SubStep.TestMultiple: + args.AddCustomItem("Aging Item 1", _ => + { + this.multipleTriggerOne = true; + }); - default: - switch (args.ParentAddonName) - { - case "Inventory": - if (this.currentSubStep != SubStep.TestItem) - return; + args.AddCustomItem("Aging Item 2", _ => + { + this.multipleTriggerTwo = true; + }); - args.AddCustomItem("Aging Item", _ => - { - this.clickedItemId = args.InventoryItemContext!.Id; - this.clickedItemHq = args.InventoryItemContext!.IsHighQuality; - this.clickedItemCount = args.InventoryItemContext!.Count; - Log.Warning("Clicked item: {Id} hq:{Hq} count:{Count}", this.clickedItemId, this.clickedItemHq, this.clickedItemCount); - }); - break; + return; + case SubStep.Finish: + return; - case null: - case "_PartyList": - case "ChatLog": - case "ContactList": - case "ContentMemberList": - case "CrossWorldLinkshell": - case "FreeCompany": - case "FriendList": - case "LookingForGroup": - case "LinkShell": - case "PartyMemberList": - case "SocialList": - if (this.currentSubStep != SubStep.TestGameObject || args.GameObjectContext == null || args.GameObjectContext.Name.IsNullOrEmpty()) - return; + default: + switch (args.ParentAddonName) + { + case "Inventory": + if (this.currentSubStep != SubStep.TestItem) + return; - args.AddCustomItem("Aging Character", _ => - { - this.clickedPlayerName = args.GameObjectContext.Name!; - this.clickedPlayerWorld = args.GameObjectContext.WorldId; - this.clickedPlayerCid = args.GameObjectContext.ContentId; - this.clickedPlayerId = args.GameObjectContext.Id; + args.AddCustomItem("Aging Item", _ => + { + this.clickedItemId = args.InventoryItemContext!.Id; + this.clickedItemHq = args.InventoryItemContext!.IsHighQuality; + this.clickedItemCount = args.InventoryItemContext!.Count; + Log.Warning("Clicked item: {Id} hq:{Hq} count:{Count}", this.clickedItemId, this.clickedItemHq, this.clickedItemCount); + }); + break; - Log.Warning("Clicked player: {Name} world:{World} cid:{Cid} id:{Id}", this.clickedPlayerName, this.clickedPlayerWorld, this.clickedPlayerCid, this.clickedPlayerId); - }); + case null: + case "_PartyList": + case "ChatLog": + case "ContactList": + case "ContentMemberList": + case "CrossWorldLinkshell": + case "FreeCompany": + case "FriendList": + case "LookingForGroup": + case "LinkShell": + case "PartyMemberList": + case "SocialList": + if (this.currentSubStep != SubStep.TestGameObject || args.GameObjectContext == null || args.GameObjectContext.Name.IsNullOrEmpty()) + return; - break; - } + args.AddCustomItem("Aging Character", _ => + { + this.clickedPlayerName = args.GameObjectContext.Name!; + this.clickedPlayerWorld = args.GameObjectContext.WorldId; + this.clickedPlayerCid = args.GameObjectContext.ContentId; + this.clickedPlayerId = args.GameObjectContext.Id; - break; - } + Log.Warning("Clicked player: {Name} world:{World} cid:{Cid} id:{Id}", this.clickedPlayerName, this.clickedPlayerWorld, this.clickedPlayerCid, this.clickedPlayerId); + }); + + break; + } + + break; } - */ } + */ } 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 1b451b73f..55e836dfb 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.Raw(GamepadButtons.North) == 1 + && gamepadState.Raw(GamepadButtons.East) == 1 + && gamepadState.Raw(GamepadButtons.L1) == 1) { - var gamepadState = Service.Get(); - - ImGui.Text("Hold down North, East, L1"); - - if (gamepadState.Raw(GamepadButtons.North) == 1 - && gamepadState.Raw(GamepadButtons.East) == 1 - && gamepadState.Raw(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/ItemPayloadAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs index 3d876e6e1..3c7282188 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs @@ -5,116 +5,115 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for item payloads. +/// +internal class ItemPayloadAgingStep : IAgingStep { - /// - /// Test setup for item payloads. - /// - internal class ItemPayloadAgingStep : IAgingStep + private SubStep currentSubStep; + + private enum SubStep { - private SubStep currentSubStep; + PrintNormalItem, + HoverNormalItem, + PrintHqItem, + HoverHqItem, + PrintCollectable, + HoverCollectable, + PrintEventItem, + HoverEventItem, + PrintNormalWithText, + HoverNormalWithText, + Done, + } - private enum SubStep + /// + public string Name => "Test Item Payloads"; + + /// + public SelfTestStepResult RunStep() + { + var gameGui = Service.Get(); + var chatGui = Service.Get(); + + const uint normalItemId = 24002; // Capybara pup + const uint hqItemId = 31861; // Exarchic circlets of healing + const uint collectableItemId = 36299; // Rarefied Annite + const uint eventItemId = 2003363; // Speude bradeos figurine + + SeString? toPrint = null; + + ImGui.Text(this.currentSubStep.ToString()); + + switch (this.currentSubStep) { - PrintNormalItem, - HoverNormalItem, - PrintHqItem, - HoverHqItem, - PrintCollectable, - HoverCollectable, - PrintEventItem, - HoverEventItem, - PrintNormalWithText, - HoverNormalWithText, - Done, + case SubStep.PrintNormalItem: + toPrint = SeString.CreateItemLink(normalItemId); + this.currentSubStep++; + break; + case SubStep.HoverNormalItem: + ImGui.Text("Hover the item."); + if (gameGui.HoveredItem != normalItemId) + return SelfTestStepResult.Waiting; + this.currentSubStep++; + break; + case SubStep.PrintHqItem: + toPrint = SeString.CreateItemLink(hqItemId, ItemPayload.ItemKind.Hq); + this.currentSubStep++; + break; + case SubStep.HoverHqItem: + ImGui.Text("Hover the item."); + if (gameGui.HoveredItem != 1_000_000 + hqItemId) + return SelfTestStepResult.Waiting; + this.currentSubStep++; + break; + case SubStep.PrintCollectable: + toPrint = SeString.CreateItemLink(collectableItemId, ItemPayload.ItemKind.Collectible); + this.currentSubStep++; + break; + case SubStep.HoverCollectable: + ImGui.Text("Hover the item."); + if (gameGui.HoveredItem != 500_000 + collectableItemId) + return SelfTestStepResult.Waiting; + this.currentSubStep++; + break; + case SubStep.PrintEventItem: + toPrint = SeString.CreateItemLink(eventItemId, ItemPayload.ItemKind.EventItem); + this.currentSubStep++; + break; + case SubStep.HoverEventItem: + ImGui.Text("Hover the item."); + if (gameGui.HoveredItem != eventItemId) + return SelfTestStepResult.Waiting; + this.currentSubStep++; + break; + case SubStep.PrintNormalWithText: + toPrint = SeString.CreateItemLink(normalItemId, displayNameOverride: "Gort"); + this.currentSubStep++; + break; + case SubStep.HoverNormalWithText: + ImGui.Text("Hover the item."); + if (gameGui.HoveredItem != normalItemId) + return SelfTestStepResult.Waiting; + this.currentSubStep++; + break; + case SubStep.Done: + return SelfTestStepResult.Pass; + default: + throw new ArgumentOutOfRangeException(); } - /// - public string Name => "Test Item Payloads"; + if (toPrint != null) + chatGui.Print(toPrint); - /// - public SelfTestStepResult RunStep() - { - var gameGui = Service.Get(); - var chatGui = Service.Get(); + return SelfTestStepResult.Waiting; + } - const uint normalItemId = 24002; // Capybara pup - const uint hqItemId = 31861; // Exarchic circlets of healing - const uint collectableItemId = 36299; // Rarefied Annite - const uint eventItemId = 2003363; // Speude bradeos figurine - - SeString? toPrint = null; - - ImGui.Text(this.currentSubStep.ToString()); - - switch (this.currentSubStep) - { - case SubStep.PrintNormalItem: - toPrint = SeString.CreateItemLink(normalItemId); - this.currentSubStep++; - break; - case SubStep.HoverNormalItem: - ImGui.Text("Hover the item."); - if (gameGui.HoveredItem != normalItemId) - return SelfTestStepResult.Waiting; - this.currentSubStep++; - break; - case SubStep.PrintHqItem: - toPrint = SeString.CreateItemLink(hqItemId, ItemPayload.ItemKind.Hq); - this.currentSubStep++; - break; - case SubStep.HoverHqItem: - ImGui.Text("Hover the item."); - if (gameGui.HoveredItem != 1_000_000 + hqItemId) - return SelfTestStepResult.Waiting; - this.currentSubStep++; - break; - case SubStep.PrintCollectable: - toPrint = SeString.CreateItemLink(collectableItemId, ItemPayload.ItemKind.Collectible); - this.currentSubStep++; - break; - case SubStep.HoverCollectable: - ImGui.Text("Hover the item."); - if (gameGui.HoveredItem != 500_000 + collectableItemId) - return SelfTestStepResult.Waiting; - this.currentSubStep++; - break; - case SubStep.PrintEventItem: - toPrint = SeString.CreateItemLink(eventItemId, ItemPayload.ItemKind.EventItem); - this.currentSubStep++; - break; - case SubStep.HoverEventItem: - ImGui.Text("Hover the item."); - if (gameGui.HoveredItem != eventItemId) - return SelfTestStepResult.Waiting; - this.currentSubStep++; - break; - case SubStep.PrintNormalWithText: - toPrint = SeString.CreateItemLink(normalItemId, displayNameOverride: "Gort"); - this.currentSubStep++; - break; - case SubStep.HoverNormalWithText: - ImGui.Text("Hover the item."); - if (gameGui.HoveredItem != normalItemId) - return SelfTestStepResult.Waiting; - this.currentSubStep++; - break; - case SubStep.Done: - return SelfTestStepResult.Pass; - default: - throw new ArgumentOutOfRangeException(); - } - - if (toPrint != null) - chatGui.Print(toPrint); - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - this.currentSubStep = SubStep.PrintNormalItem; - } + /// + public void CleanUp() + { + this.currentSubStep = SubStep.PrintNormalItem; } } 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 5819a6356..060c0bcc8 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-Out"; + + /// + public SelfTestStepResult RunStep() { - private bool subscribed = false; - private bool hasPassed = false; + var clientState = Service.Get(); - /// - public string Name => "Test Log-Out"; + 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 48121abc3..1e66ac19e 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.SoftTarget; + if (sTarget is EventObj) + { + return SelfTestStepResult.Pass; + } - break; - - case 3: - ImGui.Text("Soft-Target an EventObj..."); - - var sTarget = targetManager.SoftTarget; - 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..5abc8e2ed 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 0af8bf1bb..352c5d322 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -11,249 +11,248 @@ 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 ItemPayloadAgingStep(), + new ContextMenuAgingStep(), + new ActorTableAgingStep(), + new FateTableAgingStep(), + new AetheryteListAgingStep(), + 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 ItemPayloadAgingStep(), - new ContextMenuAgingStep(), - new ActorTableAgingStep(), - new FateTableAgingStep(), - new AetheryteListAgingStep(), - 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 a088f449e..3fb666af2 100644 --- a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs @@ -17,961 +17,960 @@ using Dalamud.Plugin.Internal; using Dalamud.Utility; 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 readonly string[] languages; + private readonly string[] locLanguages; + + private int langIndex; + + private XivChatType dalamudMessagesChatType; + + private bool doWaitForPluginsOnStartup; + private bool doCfTaskBarFlash; + private bool doCfChatMessage; + private bool doMbCollect; + + private float globalUiScale; + private bool doUseAxisFontsFromGame; + private float fontGamma; + private bool doToggleUiHide; + private bool doToggleUiHideDuringCutscenes; + private bool doToggleUiHideDuringGpose; + private bool doDocking; + private bool doViewport; + private bool doGamepad; + private bool doFocus; + private bool doTsm; + + private List? dtrOrder; + private List? dtrIgnore; + private int dtrSpacing; + private bool dtrSwapDirection; + + private int? pluginWaitBeforeFree; + + 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 readonly string[] languages; - private readonly string[] locLanguages; + var configuration = Service.Get(); - private int langIndex; + this.Size = new Vector2(740, 550); + this.SizeCondition = ImGuiCond.FirstUseEver; - private XivChatType dalamudMessagesChatType; + this.dalamudMessagesChatType = configuration.GeneralChatType; - private bool doWaitForPluginsOnStartup; - private bool doCfTaskBarFlash; - private bool doCfChatMessage; - private bool doMbCollect; + this.doWaitForPluginsOnStartup = configuration.IsResumeGameAfterPluginLoad; + this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; + this.doCfChatMessage = configuration.DutyFinderChatMessage; + this.doMbCollect = configuration.IsMbCollect; - private float globalUiScale; - private bool doUseAxisFontsFromGame; - private float fontGamma; - private bool doToggleUiHide; - private bool doToggleUiHideDuringCutscenes; - private bool doToggleUiHideDuringGpose; - private bool doDocking; - private bool doViewport; - private bool doGamepad; - private bool doFocus; - private bool doTsm; + this.globalUiScale = configuration.GlobalUiScale; + this.fontGamma = configuration.FontGammaLevel; + this.doUseAxisFontsFromGame = configuration.UseAxisFontsFromGame; + this.doToggleUiHide = configuration.ToggleUiHide; + this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes; + this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose; - private List? dtrOrder; - private List? dtrIgnore; - private int dtrSpacing; - private bool dtrSwapDirection; + this.doDocking = configuration.IsDocking; + this.doViewport = !configuration.IsDisableViewport; + this.doGamepad = configuration.IsGamepadNavigationEnabled; + this.doFocus = configuration.IsFocusManagementEnabled; + this.doTsm = configuration.ShowTsm; - private int? pluginWaitBeforeFree; + this.dtrSpacing = configuration.DtrSpacing; + this.dtrSwapDirection = configuration.DtrSwapDirection; - private List thirdRepoList; - private bool thirdRepoListChanged; - private string thirdRepoTempUrl = string.Empty; - private string thirdRepoAddError = string.Empty; + this.pluginWaitBeforeFree = configuration.PluginWaitBeforeFree; - 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 + this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); + this.langIndex = Array.IndexOf(this.languages, configuration.EffectiveLanguage); + if (this.langIndex == -1) + this.langIndex = 0; - private bool doPluginTest; + 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 + { + locLanguagesList.Add("Korean"); + } + } + + 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; + + var configuration = Service.Get(); + this.dtrOrder = configuration.DtrOrder; + this.dtrIgnore = configuration.DtrIgnore; + } + + /// + public override void OnClose() + { + var configuration = Service.Get(); + var interfaceManager = Service.Get(); + + var rebuildFont = ImGui.GetIO().FontGlobalScale != configuration.GlobalUiScale + || interfaceManager.FontGamma != configuration.FontGammaLevel + || interfaceManager.UseAxis != configuration.UseAxisFontsFromGame; + + ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; + interfaceManager.FontGammaOverride = null; + interfaceManager.UseAxisOverride = null; + this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); + this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); + + configuration.DtrOrder = this.dtrOrder; + configuration.DtrIgnore = this.dtrIgnore; + + if (rebuildFont) + interfaceManager.RebuildFonts(); + } + + /// + 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); + + if (ImGui.BeginTabBar("SetTabBar")) + { + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General") + "###settingsTabGeneral")) + { + this.DrawGeneralTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel") + "###settingsTabVisual")) + { + this.DrawLookAndFeelTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsServerInfoBar", "Server Info Bar") + "###settingsTabInfoBar")) + { + this.DrawServerInfoBarTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental") + "###settingsTabExperimental")) + { + this.DrawExperimentalTab(); + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + + ImGui.EndChild(); + + this.DrawSaveCloseButtons(); + } + + 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)) + { + 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("DalamudSettingsWaitForPluginsOnStartup", "Wait for plugins before game loads"), ref this.doWaitForPluginsOnStartup); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsWaitForPluginsOnStartupHint", "Do not let the game load, until plugins are loaded.")); + + 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.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Checkbox(Loc.Localize("DalamudSettingDoMbCollect", "Anonymously upload market board data"), ref this.doMbCollect); + + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey); + ImGui.TextWrapped(Loc.Localize("DalamudSettingDoMbCollectHint", "Anonymously provide data about in-game economics to Universalis when browsing the market board. This data can't be tied to you in any way and everyone benefits!")); + ImGui.PopStyleColor(); + } + + private void DrawLookAndFeelTab() + { + var interfaceManager = Service.Get(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); + ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale")); + ImGui.SameLine(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); + if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96")) + { + this.globalUiScale = 9.6f / 12.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + interfaceManager.RebuildFonts(); + } + + ImGui.SameLine(); + if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12")) + { + this.globalUiScale = 1.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + interfaceManager.RebuildFonts(); + } + + ImGui.SameLine(); + if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14")) + { + this.globalUiScale = 14.0f / 12.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + interfaceManager.RebuildFonts(); + } + + ImGui.SameLine(); + if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18")) + { + this.globalUiScale = 18.0f / 12.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + interfaceManager.RebuildFonts(); + } + + ImGui.SameLine(); + if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36")) + { + this.globalUiScale = 36.0f / 12.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + interfaceManager.RebuildFonts(); + } + + var globalUiScaleInPt = 12f * this.globalUiScale; + if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp)) + { + this.globalUiScale = globalUiScaleInPt / 12f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + interfaceManager.RebuildFonts(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale text in all XIVLauncher UI elements - this is 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); + + if (ImGui.Checkbox(Loc.Localize("DalamudSettingToggleAxisFonts", "Use AXIS fonts as default Dalamud font"), ref this.doUseAxisFontsFromGame)) + { + interfaceManager.UseAxisOverride = this.doUseAxisFontsFromGame; + interfaceManager.RebuildFonts(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiAxisFontsHint", "Use AXIS fonts (the game's main UI fonts) as default Dalamud font.")); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")); + + ImGuiHelpers.ScaledDummy(3); + + 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.Checkbox(Loc.Localize("DalamudSettingToggleTsm", "Show title screen menu"), ref this.doTsm); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen.")); + + ImGuiHelpers.ScaledDummy(10, 16); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); + ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma")); + ImGui.SameLine(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); + if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset")) + { + this.fontGamma = 1.4f; + interfaceManager.FontGammaOverride = this.fontGamma; + interfaceManager.RebuildFonts(); + } + + if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp)) + { + interfaceManager.FontGammaOverride = this.fontGamma; + interfaceManager.RebuildFonts(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFontGammaHint", "Changes the thickness of text.")); + + ImGuiHelpers.ScaledDummy(10, 16); + } + + private void DrawServerInfoBarTab() + { + ImGui.Text(Loc.Localize("DalamudSettingServerInfoBar", "Server Info Bar configuration")); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarHint", "Plugins can put additional information into your server information bar(where world & time can be seen).\nYou can reorder and disable these here.")); + + ImGuiHelpers.ScaledDummy(10); + + var configuration = Service.Get(); + var dtrBar = Service.Get(); + + var order = configuration.DtrOrder!.Where(x => dtrBar.HasEntry(x)).ToList(); + var ignore = configuration.DtrIgnore!.Where(x => dtrBar.HasEntry(x)).ToList(); + var orderLeft = configuration.DtrOrder!.Where(x => !order.Contains(x)).ToList(); + var ignoreLeft = configuration.DtrIgnore!.Where(x => !ignore.Contains(x)).ToList(); + + if (order.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDidNone", "You have no plugins that use this feature.")); + } + + var isOrderChange = false; + for (var i = 0; i < order.Count; i++) + { + var title = order[i]; + + // TODO: Maybe we can also resort the rest of the bar in the future? + // var isRequired = search is Configuration.SearchSetting.Internal or Configuration.SearchSetting.MacroLinks; + + ImGui.PushFont(UiBuilder.IconFont); + + var arrowUpText = $"{FontAwesomeIcon.ArrowUp.ToIconString()}##{title}"; + if (i == 0) + { + ImGuiComponents.DisabledButton(arrowUpText); + } + else + { + if (ImGui.Button(arrowUpText)) + { + (order[i], order[i - 1]) = (order[i - 1], order[i]); + isOrderChange = true; + } + } + + ImGui.SameLine(); + + var arrowDownText = $"{FontAwesomeIcon.ArrowDown.ToIconString()}##{title}"; + if (i == order.Count - 1) + { + ImGuiComponents.DisabledButton(arrowDownText); + } + else + { + if (ImGui.Button(arrowDownText) && i != order.Count - 1) + { + (order[i], order[i + 1]) = (order[i + 1], order[i]); + isOrderChange = true; + } + } + + ImGui.PopFont(); + + ImGui.SameLine(); + + // if (isRequired) { + // ImGui.TextUnformatted($"Search in {name}"); + // } else { + + var isShown = ignore.All(x => x != title); + var nextIsShow = isShown; + if (ImGui.Checkbox($"{title}###dtrEntry{i}", ref nextIsShow) && nextIsShow != isShown) + { + if (nextIsShow) + ignore.Remove(title); + else + ignore.Add(title); + + dtrBar.MakeDirty(title); + } + + // } + } + + configuration.DtrOrder = order.Concat(orderLeft).ToList(); + configuration.DtrIgnore = ignore.Concat(ignoreLeft).ToList(); + + if (isOrderChange) + dtrBar.ApplySort(); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarSpacing", "Server Info Bar spacing")); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarSpacingHint", "Configure the amount of space between entries in the server info bar here.")); + ImGui.SliderInt("Spacing", ref this.dtrSpacing, 0, 40); + + ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarDirection", "Server Info Bar direction")); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDirectionHint", "If checked, the Server Info Bar elements will expand to the right instead of the left.")); + ImGui.Checkbox("Swap Direction", ref this.dtrSwapDirection); + } + + private void DrawExperimentalTab() + { + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + var useCustomPluginWaitBeforeFree = this.pluginWaitBeforeFree.HasValue; + if (ImGui.Checkbox( + Loc.Localize("DalamudSettingsPluginCustomizeWaitTime", "Customize wait time for plugin unload"), + ref useCustomPluginWaitBeforeFree)) + { + if (!useCustomPluginWaitBeforeFree) + this.pluginWaitBeforeFree = null; + else + this.pluginWaitBeforeFree = PluginManager.PluginWaitBeforeFreeDefault; + } + + if (useCustomPluginWaitBeforeFree) + { + var waitTime = this.pluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault; + if (ImGui.SliderInt( + "Wait time###DalamudSettingsPluginCustomizeWaitTimeSlider", + ref waitTime, + 0, + 5000)) + { + this.pluginWaitBeforeFree = waitTime; + } + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginCustomizeWaitTimeHint", "Configure the wait time between stopping plugin and completely unloading plugin. If you are experiencing crashes when exiting the game, try increasing this value.")); + + ImGuiHelpers.ScaledDummy(12); + + #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 - /// - /// Initializes a new instance of the class. - /// - public SettingsWindow() - : base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse) + ImGuiHelpers.ScaledDummy(12); + + #region Hidden plugins + + if (ImGui.Button(Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"))) { - var configuration = Service.Get(); - - this.Size = new Vector2(740, 550); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.dalamudMessagesChatType = configuration.GeneralChatType; - - this.doWaitForPluginsOnStartup = configuration.IsResumeGameAfterPluginLoad; - this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; - this.doCfChatMessage = configuration.DutyFinderChatMessage; - this.doMbCollect = configuration.IsMbCollect; - - this.globalUiScale = configuration.GlobalUiScale; - this.fontGamma = configuration.FontGammaLevel; - this.doUseAxisFontsFromGame = configuration.UseAxisFontsFromGame; - 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.doTsm = configuration.ShowTsm; - - this.dtrSpacing = configuration.DtrSpacing; - this.dtrSwapDirection = configuration.DtrSwapDirection; - - this.pluginWaitBeforeFree = configuration.PluginWaitBeforeFree; - - 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(); - this.langIndex = Array.IndexOf(this.languages, configuration.EffectiveLanguage); - if (this.langIndex == -1) - 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 - { - locLanguagesList.Add("Korean"); - } - } - - this.locLanguages = locLanguagesList.ToArray(); - } - catch (Exception) - { - this.locLanguages = this.languages; // Languages not localized, only codes. - } + configuration.HiddenPluginInternalName.Clear(); + pluginManager.RefilterPluginMasters(); } - /// - public override void OnOpen() + 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: " + Util.FormatBytes(GC.GetTotalMemory(false))); + } + + private void DrawCustomReposSection() + { + ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories")); + if (this.thirdRepoListChanged) { - this.thirdRepoListChanged = false; - this.devPluginLocationsChanged = false; - - var configuration = Service.Get(); - this.dtrOrder = configuration.DtrOrder; - this.dtrIgnore = configuration.DtrIgnore; - } - - /// - public override void OnClose() - { - var configuration = Service.Get(); - var interfaceManager = Service.Get(); - - var rebuildFont = ImGui.GetIO().FontGlobalScale != configuration.GlobalUiScale - || interfaceManager.FontGamma != configuration.FontGammaLevel - || interfaceManager.UseAxis != configuration.UseAxisFontsFromGame; - - ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; - interfaceManager.FontGammaOverride = null; - interfaceManager.UseAxisOverride = null; - this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); - this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); - - configuration.DtrOrder = this.dtrOrder; - configuration.DtrIgnore = this.dtrIgnore; - - if (rebuildFont) - interfaceManager.RebuildFonts(); - } - - /// - 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); - - if (ImGui.BeginTabBar("SetTabBar")) - { - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General") + "###settingsTabGeneral")) - { - this.DrawGeneralTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel") + "###settingsTabVisual")) - { - this.DrawLookAndFeelTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsServerInfoBar", "Server Info Bar") + "###settingsTabInfoBar")) - { - this.DrawServerInfoBarTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental") + "###settingsTabExperimental")) - { - this.DrawExperimentalTab(); - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); - } - - ImGui.EndChild(); - - this.DrawSaveCloseButtons(); - } - - 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)) - { - 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("DalamudSettingsWaitForPluginsOnStartup", "Wait for plugins before game loads"), ref this.doWaitForPluginsOnStartup); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsWaitForPluginsOnStartupHint", "Do not let the game load, until plugins are loaded.")); - - 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.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Checkbox(Loc.Localize("DalamudSettingDoMbCollect", "Anonymously upload market board data"), ref this.doMbCollect); - - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey); - ImGui.TextWrapped(Loc.Localize("DalamudSettingDoMbCollectHint", "Anonymously provide data about in-game economics to Universalis when browsing the market board. This data can't be tied to you in any way and everyone benefits!")); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + ImGui.SameLine(); + ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); ImGui.PopStyleColor(); } - private void DrawLookAndFeelTab() + 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.")); + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning2", "Plugins have full control over your PC, like any other program, and may cause harm or crashes.")); + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning3", "Please make absolutely sure that you only install third-party plugins from developers you trust.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Columns(4); + ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (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 interfaceManager = Service.Get(); + var isEnabled = thirdRepoSetting.IsEnabled; - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); - ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale")); - ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); - if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96")) - { - this.globalUiScale = 9.6f / 12.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - interfaceManager.RebuildFonts(); - } - - ImGui.SameLine(); - if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12")) - { - this.globalUiScale = 1.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - interfaceManager.RebuildFonts(); - } - - ImGui.SameLine(); - if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14")) - { - this.globalUiScale = 14.0f / 12.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - interfaceManager.RebuildFonts(); - } - - ImGui.SameLine(); - if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18")) - { - this.globalUiScale = 18.0f / 12.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - interfaceManager.RebuildFonts(); - } - - ImGui.SameLine(); - if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36")) - { - this.globalUiScale = 36.0f / 12.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - interfaceManager.RebuildFonts(); - } - - var globalUiScaleInPt = 12f * this.globalUiScale; - if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp)) - { - this.globalUiScale = globalUiScaleInPt / 12f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - interfaceManager.RebuildFonts(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale text in all XIVLauncher UI elements - this is 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); - - if (ImGui.Checkbox(Loc.Localize("DalamudSettingToggleAxisFonts", "Use AXIS fonts as default Dalamud font"), ref this.doUseAxisFontsFromGame)) - { - interfaceManager.UseAxisOverride = this.doUseAxisFontsFromGame; - interfaceManager.RebuildFonts(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiAxisFontsHint", "Use AXIS fonts (the game's main UI fonts) as default Dalamud font.")); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")); - - ImGuiHelpers.ScaledDummy(3); - - 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.Checkbox(Loc.Localize("DalamudSettingToggleTsm", "Show title screen menu"), ref this.doTsm); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen.")); - - ImGuiHelpers.ScaledDummy(10, 16); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); - ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma")); - ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); - if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset")) - { - this.fontGamma = 1.4f; - interfaceManager.FontGammaOverride = this.fontGamma; - interfaceManager.RebuildFonts(); - } - - if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp)) - { - interfaceManager.FontGammaOverride = this.fontGamma; - interfaceManager.RebuildFonts(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFontGammaHint", "Changes the thickness of text.")); - - ImGuiHelpers.ScaledDummy(10, 16); - } - - private void DrawServerInfoBarTab() - { - ImGui.Text(Loc.Localize("DalamudSettingServerInfoBar", "Server Info Bar configuration")); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarHint", "Plugins can put additional information into your server information bar(where world & time can be seen).\nYou can reorder and disable these here.")); - - ImGuiHelpers.ScaledDummy(10); - - var configuration = Service.Get(); - var dtrBar = Service.Get(); - - var order = configuration.DtrOrder!.Where(x => dtrBar.HasEntry(x)).ToList(); - var ignore = configuration.DtrIgnore!.Where(x => dtrBar.HasEntry(x)).ToList(); - var orderLeft = configuration.DtrOrder!.Where(x => !order.Contains(x)).ToList(); - var ignoreLeft = configuration.DtrIgnore!.Where(x => !ignore.Contains(x)).ToList(); - - if (order.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDidNone", "You have no plugins that use this feature.")); - } - - var isOrderChange = false; - for (var i = 0; i < order.Count; i++) - { - var title = order[i]; - - // TODO: Maybe we can also resort the rest of the bar in the future? - // var isRequired = search is Configuration.SearchSetting.Internal or Configuration.SearchSetting.MacroLinks; - - ImGui.PushFont(UiBuilder.IconFont); - - var arrowUpText = $"{FontAwesomeIcon.ArrowUp.ToIconString()}##{title}"; - if (i == 0) - { - ImGuiComponents.DisabledButton(arrowUpText); - } - else - { - if (ImGui.Button(arrowUpText)) - { - (order[i], order[i - 1]) = (order[i - 1], order[i]); - isOrderChange = true; - } - } - - ImGui.SameLine(); - - var arrowDownText = $"{FontAwesomeIcon.ArrowDown.ToIconString()}##{title}"; - if (i == order.Count - 1) - { - ImGuiComponents.DisabledButton(arrowDownText); - } - else - { - if (ImGui.Button(arrowDownText) && i != order.Count - 1) - { - (order[i], order[i + 1]) = (order[i + 1], order[i]); - isOrderChange = true; - } - } - - ImGui.PopFont(); - - ImGui.SameLine(); - - // if (isRequired) { - // ImGui.TextUnformatted($"Search in {name}"); - // } else { - - var isShown = ignore.All(x => x != title); - var nextIsShow = isShown; - if (ImGui.Checkbox($"{title}###dtrEntry{i}", ref nextIsShow) && nextIsShow != isShown) - { - if (nextIsShow) - ignore.Remove(title); - else - ignore.Add(title); - - dtrBar.MakeDirty(title); - } - - // } - } - - configuration.DtrOrder = order.Concat(orderLeft).ToList(); - configuration.DtrIgnore = ignore.Concat(ignoreLeft).ToList(); - - if (isOrderChange) - dtrBar.ApplySort(); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarSpacing", "Server Info Bar spacing")); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarSpacingHint", "Configure the amount of space between entries in the server info bar here.")); - ImGui.SliderInt("Spacing", ref this.dtrSpacing, 0, 40); - - ImGui.Text(Loc.Localize("DalamudSettingServerInfoBarDirection", "Server Info Bar direction")); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingServerInfoBarDirectionHint", "If checked, the Server Info Bar elements will expand to the right instead of the left.")); - ImGui.Checkbox("Swap Direction", ref this.dtrSwapDirection); - } - - private void DrawExperimentalTab() - { - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - var useCustomPluginWaitBeforeFree = this.pluginWaitBeforeFree.HasValue; - if (ImGui.Checkbox( - Loc.Localize("DalamudSettingsPluginCustomizeWaitTime", "Customize wait time for plugin unload"), - ref useCustomPluginWaitBeforeFree)) - { - if (!useCustomPluginWaitBeforeFree) - this.pluginWaitBeforeFree = null; - else - this.pluginWaitBeforeFree = PluginManager.PluginWaitBeforeFreeDefault; - } - - if (useCustomPluginWaitBeforeFree) - { - var waitTime = this.pluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault; - if (ImGui.SliderInt( - "Wait time###DalamudSettingsPluginCustomizeWaitTimeSlider", - ref waitTime, - 0, - 5000)) - { - this.pluginWaitBeforeFree = waitTime; - } - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginCustomizeWaitTimeHint", "Configure the wait time between stopping plugin and completely unloading plugin. If you are experiencing crashes when exiting the game, try increasing this value.")); - - ImGuiHelpers.ScaledDummy(12); - - #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: " + Util.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.")); - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning2", "Plugins have full control over your PC, like any other program, and may cause harm or crashes.")); - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning3", "Please make absolutely sure that you only install third-party plugins from developers you trust.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (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.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (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.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - (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.Replace("\"", string.Empty), - 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.IsResumeGameAfterPluginLoad = this.doWaitForPluginsOnStartup; - configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; - configuration.DutyFinderChatMessage = this.doCfChatMessage; - configuration.IsMbCollect = this.doMbCollect; - - 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; - configuration.ShowTsm = this.doTsm; - - configuration.UseAxisFontsFromGame = this.doUseAxisFontsFromGame; - configuration.FontGammaLevel = this.fontGamma; - - // 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.Replace("\"", string.Empty), + 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; + ImGui.Columns(1); - var di = Service.Get(); - di.CloseGamepadModeNotifierWindow(); - } - else - { - ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; - } - - this.dtrOrder = configuration.DtrOrder; - this.dtrIgnore = configuration.DtrIgnore; - - configuration.DtrSpacing = this.dtrSpacing; - configuration.DtrSwapDirection = this.dtrSwapDirection; - - configuration.PluginWaitBeforeFree = this.pluginWaitBeforeFree; - - 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(); - Service.Get().RebuildFonts(); + 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.IsResumeGameAfterPluginLoad = this.doWaitForPluginsOnStartup; + configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; + configuration.DutyFinderChatMessage = this.doCfChatMessage; + configuration.IsMbCollect = this.doMbCollect; + + 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; + configuration.ShowTsm = this.doTsm; + + configuration.UseAxisFontsFromGame = this.doUseAxisFontsFromGame; + configuration.FontGammaLevel = this.fontGamma; + + // 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; + + var di = Service.Get(); + di.CloseGamepadModeNotifierWindow(); + } + else + { + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; + } + + this.dtrOrder = configuration.DtrOrder; + this.dtrIgnore = configuration.DtrIgnore; + + configuration.DtrSpacing = this.dtrSpacing; + configuration.DtrSwapDirection = this.dtrSwapDirection; + + configuration.PluginWaitBeforeFree = this.pluginWaitBeforeFree; + + 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(); + Service.Get().RebuildFonts(); + } } 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/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 78581e624..3d8fadb53 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -13,364 +13,363 @@ using Dalamud.Interface.Windowing; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// Class responsible for drawing the main plugin window. +/// +internal class TitleScreenMenuWindow : Window, IDisposable { + private const float TargetFontSizePt = 18f; + private const float TargetFontSizePx = TargetFontSizePt * 4 / 3; + + private readonly TextureWrap shadeTexture; + + private readonly Dictionary shadeEasings = new(); + private readonly Dictionary moveEasings = new(); + private readonly Dictionary logoEasings = new(); + private readonly Dictionary specialGlyphRequests = new(); + + private InOutCubic? fadeOutEasing; + + private State state = State.Hide; + /// - /// Class responsible for drawing the main plugin window. + /// Initializes a new instance of the class. /// - internal class TitleScreenMenuWindow : Window, IDisposable + public TitleScreenMenuWindow() + : base( + "TitleScreenMenuOverlay", + ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | + ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus) { - private const float TargetFontSizePt = 18f; - private const float TargetFontSizePx = TargetFontSizePt * 4 / 3; + this.IsOpen = true; - private readonly TextureWrap shadeTexture; + this.ForceMainWindow = true; - private readonly Dictionary shadeEasings = new(); - private readonly Dictionary moveEasings = new(); - private readonly Dictionary logoEasings = new(); - private readonly Dictionary specialGlyphRequests = new(); + this.Position = new Vector2(0, 200); + this.PositionCondition = ImGuiCond.Always; + this.RespectCloseHotkey = false; - private InOutCubic? fadeOutEasing; + var dalamud = Service.Get(); + var interfaceManager = Service.Get(); - private State state = State.Hide; + var shadeTex = + interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png")); + this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture."); - /// - /// Initializes a new instance of the class. - /// - public TitleScreenMenuWindow() - : base( - "TitleScreenMenuOverlay", - ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus) + var framework = Service.Get(); + framework.Update += this.FrameworkOnUpdate; + } + + private enum State + { + Hide, + Show, + FadeOut, + } + + /// + public override void PreDraw() + { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); + base.PreDraw(); + } + + /// + public override void PostDraw() + { + ImGui.PopStyleVar(2); + base.PostDraw(); + } + + /// + public void Dispose() + { + this.shadeTexture.Dispose(); + var framework = Service.Get(); + framework.Update -= this.FrameworkOnUpdate; + } + + /// + public override void Draw() + { + var scale = ImGui.GetIO().FontGlobalScale; + + var tsm = Service.Get(); + + switch (this.state) { - this.IsOpen = true; - - this.ForceMainWindow = true; - - this.Position = new Vector2(0, 200); - this.PositionCondition = ImGuiCond.Always; - this.RespectCloseHotkey = false; - - var dalamud = Service.Get(); - var interfaceManager = Service.Get(); - - var shadeTex = - interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png")); - this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture."); - - var framework = Service.Get(); - framework.Update += this.FrameworkOnUpdate; - } - - private enum State - { - Hide, - Show, - FadeOut, - } - - /// - public override void PreDraw() - { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); - base.PreDraw(); - } - - /// - public override void PostDraw() - { - ImGui.PopStyleVar(2); - base.PostDraw(); - } - - /// - public void Dispose() - { - this.shadeTexture.Dispose(); - var framework = Service.Get(); - framework.Update -= this.FrameworkOnUpdate; - } - - /// - public override void Draw() - { - var scale = ImGui.GetIO().FontGlobalScale; - - var tsm = Service.Get(); - - switch (this.state) + case State.Show: { - case State.Show: + for (var i = 0; i < tsm.Entries.Count; i++) + { + var entry = tsm.Entries[i]; + + if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing)) { - for (var i = 0; i < tsm.Entries.Count; i++) - { - var entry = tsm.Entries[i]; - - if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing)) - { - moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400)); - this.moveEasings.Add(entry.Id, moveEasing); - } - - if (!moveEasing.IsRunning && !moveEasing.IsDone) - { - moveEasing.Restart(); - } - - if (moveEasing.IsDone) - { - moveEasing.Stop(); - } - - moveEasing.Update(); - - var finalPos = (i + 1) * this.shadeTexture.Height * scale; - var pos = moveEasing.Value * finalPos; - - // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment. - if (moveEasing.IsDone) - { - pos = finalPos; - } - - this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true); - - var cursor = ImGui.GetCursorPos(); - cursor.Y = (float)pos; - ImGui.SetCursorPos(cursor); - } - - if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows | - ImGuiHoveredFlags.AllowWhenBlockedByActiveItem)) - { - this.state = State.FadeOut; - } - - break; + moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400)); + this.moveEasings.Add(entry.Id, moveEasing); } - case State.FadeOut: + if (!moveEasing.IsRunning && !moveEasing.IsDone) { - this.fadeOutEasing ??= new InOutCubic(TimeSpan.FromMilliseconds(400)) - { - IsInverse = true, - }; - - if (!this.fadeOutEasing.IsRunning && !this.fadeOutEasing.IsDone) - { - this.fadeOutEasing.Restart(); - } - - if (this.fadeOutEasing.IsDone) - { - this.fadeOutEasing.Stop(); - } - - this.fadeOutEasing.Update(); - - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value); - - for (var i = 0; i < tsm.Entries.Count; i++) - { - var entry = tsm.Entries[i]; - - var finalPos = (i + 1) * this.shadeTexture.Height * scale; - - this.DrawEntry(entry, i != 0, true, i == 0, false); - - var cursor = ImGui.GetCursorPos(); - cursor.Y = finalPos; - ImGui.SetCursorPos(cursor); - } - - ImGui.PopStyleVar(); - - var isHover = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows | - ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); - - if (!isHover && this.fadeOutEasing!.IsDone) - { - this.state = State.Hide; - this.fadeOutEasing = null; - } - else if (isHover) - { - this.state = State.Show; - this.fadeOutEasing = null; - } - - break; + moveEasing.Restart(); } - case State.Hide: + if (moveEasing.IsDone) { - if (this.DrawEntry(tsm.Entries[0], true, false, true, true)) - { - this.state = State.Show; - } - - this.moveEasings.Clear(); - this.logoEasings.Clear(); - this.shadeEasings.Clear(); - break; + moveEasing.Stop(); } + + moveEasing.Update(); + + var finalPos = (i + 1) * this.shadeTexture.Height * scale; + var pos = moveEasing.Value * finalPos; + + // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment. + if (moveEasing.IsDone) + { + pos = finalPos; + } + + this.DrawEntry(entry, moveEasing.IsRunning && i != 0, true, i == 0, true); + + var cursor = ImGui.GetCursorPos(); + cursor.Y = (float)pos; + ImGui.SetCursorPos(cursor); + } + + if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows | + ImGuiHoveredFlags.AllowWhenBlockedByActiveItem)) + { + this.state = State.FadeOut; + } + + break; } - var srcText = tsm.Entries.Select(e => e.Name).ToHashSet(); - var keys = this.specialGlyphRequests.Keys.ToHashSet(); - keys.RemoveWhere(x => srcText.Contains(x)); - foreach (var key in keys) + case State.FadeOut: { - this.specialGlyphRequests[key].Dispose(); - this.specialGlyphRequests.Remove(key); - } - } + this.fadeOutEasing ??= new InOutCubic(TimeSpan.FromMilliseconds(400)) + { + IsInverse = true, + }; - private bool DrawEntry( - TitleScreenMenu.TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha) - { - InterfaceManager.SpecialGlyphRequest fontHandle; - if (this.specialGlyphRequests.TryGetValue(entry.Name, out fontHandle) && fontHandle.Size != TargetFontSizePx) - { - fontHandle.Dispose(); - this.specialGlyphRequests.Remove(entry.Name); - fontHandle = null; - } + if (!this.fadeOutEasing.IsRunning && !this.fadeOutEasing.IsDone) + { + this.fadeOutEasing.Restart(); + } - if (fontHandle == null) - this.specialGlyphRequests[entry.Name] = fontHandle = Service.Get().NewFontSizeRef(TargetFontSizePx, entry.Name); + if (this.fadeOutEasing.IsDone) + { + this.fadeOutEasing.Stop(); + } - ImGui.PushFont(fontHandle.Font); - ImGui.SetWindowFontScale(TargetFontSizePx / fontHandle.Size); + this.fadeOutEasing.Update(); - var scale = ImGui.GetIO().FontGlobalScale; + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value); - if (!this.shadeEasings.TryGetValue(entry.Id, out var shadeEasing)) - { - shadeEasing = new InOutCubic(TimeSpan.FromMilliseconds(350)); - this.shadeEasings.Add(entry.Id, shadeEasing); - } + for (var i = 0; i < tsm.Entries.Count; i++) + { + var entry = tsm.Entries[i]; - var initialCursor = ImGui.GetCursorPos(); + var finalPos = (i + 1) * this.shadeTexture.Height * scale; - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)shadeEasing.Value); - ImGui.Image(this.shadeTexture.ImGuiHandle, new Vector2(this.shadeTexture.Width * scale, this.shadeTexture.Height * scale)); - ImGui.PopStyleVar(); + this.DrawEntry(entry, i != 0, true, i == 0, false); - var isHover = ImGui.IsItemHovered(); - if (isHover && (!shadeEasing.IsRunning || (shadeEasing.IsDone && shadeEasing.IsInverse)) && !inhibitFadeout) - { - shadeEasing.IsInverse = false; - shadeEasing.Restart(); - } - else if (!isHover && !shadeEasing.IsInverse && shadeEasing.IsRunning && !inhibitFadeout) - { - shadeEasing.IsInverse = true; - shadeEasing.Restart(); - } + var cursor = ImGui.GetCursorPos(); + cursor.Y = finalPos; + ImGui.SetCursorPos(cursor); + } - var isClick = ImGui.IsItemClicked(); - if (isClick) - { - entry.Trigger(); - } - - shadeEasing.Update(); - - if (!this.logoEasings.TryGetValue(entry.Id, out var logoEasing)) - { - logoEasing = new InOutCubic(TimeSpan.FromMilliseconds(350)); - this.logoEasings.Add(entry.Id, logoEasing); - } - - if (!logoEasing.IsRunning && !logoEasing.IsDone) - { - logoEasing.Restart(); - } - - if (logoEasing.IsDone) - { - logoEasing.Stop(); - } - - logoEasing.Update(); - - ImGui.SetCursorPos(initialCursor); - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - if (overrideAlpha) - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.Value); - } - else if (isFirst) - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f); - } - - ImGui.Image(entry.Texture.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale)); - if (overrideAlpha || isFirst) - { ImGui.PopStyleVar(); + + var isHover = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows | + ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); + + if (!isHover && this.fadeOutEasing!.IsDone) + { + this.state = State.Hide; + this.fadeOutEasing = null; + } + else if (isHover) + { + this.state = State.Show; + this.fadeOutEasing = null; + } + + break; } - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(10); - ImGui.SameLine(); - - var textHeight = ImGui.GetTextLineHeightWithSpacing(); - var cursor = ImGui.GetCursorPos(); - - cursor.Y += (entry.Texture.Height * scale / 2) - (textHeight / 2); - - if (overrideAlpha) + case State.Hide: { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); + if (this.DrawEntry(tsm.Entries[0], true, false, true, true)) + { + this.state = State.Show; + } + + this.moveEasings.Clear(); + this.logoEasings.Clear(); + this.shadeEasings.Clear(); + break; } - - // Drop shadow - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF000000); - for (int i = 0, i_ = (int)Math.Ceiling(1 * scale); i < i_; i++) - { - ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i)); - ImGui.Text(entry.Name); - } - - ImGui.PopStyleColor(); - - ImGui.SetCursorPos(cursor); - ImGui.Text(entry.Name); - - if (overrideAlpha) - { - ImGui.PopStyleVar(); - } - - initialCursor.Y += entry.Texture.Height * scale; - ImGui.SetCursorPos(initialCursor); - - ImGui.PopFont(); - - return isHover; } - private void FrameworkOnUpdate(Framework framework) + var srcText = tsm.Entries.Select(e => e.Name).ToHashSet(); + var keys = this.specialGlyphRequests.Keys.ToHashSet(); + keys.RemoveWhere(x => srcText.Contains(x)); + foreach (var key in keys) { - var clientState = Service.Get(); - this.IsOpen = !clientState.IsLoggedIn; - - var configuration = Service.Get(); - if (!configuration.ShowTsm) - this.IsOpen = false; - - var gameGui = Service.Get(); - var charaSelect = gameGui.GetAddonByName("CharaSelect", 1); - var charaMake = gameGui.GetAddonByName("CharaMake", 1); - var titleDcWorldMap = gameGui.GetAddonByName("TitleDCWorldMap", 1); - if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero) - this.IsOpen = false; + this.specialGlyphRequests[key].Dispose(); + this.specialGlyphRequests.Remove(key); } } + + private bool DrawEntry( + TitleScreenMenu.TitleScreenMenuEntry entry, bool inhibitFadeout, bool showText, bool isFirst, bool overrideAlpha) + { + InterfaceManager.SpecialGlyphRequest fontHandle; + if (this.specialGlyphRequests.TryGetValue(entry.Name, out fontHandle) && fontHandle.Size != TargetFontSizePx) + { + fontHandle.Dispose(); + this.specialGlyphRequests.Remove(entry.Name); + fontHandle = null; + } + + if (fontHandle == null) + this.specialGlyphRequests[entry.Name] = fontHandle = Service.Get().NewFontSizeRef(TargetFontSizePx, entry.Name); + + ImGui.PushFont(fontHandle.Font); + ImGui.SetWindowFontScale(TargetFontSizePx / fontHandle.Size); + + var scale = ImGui.GetIO().FontGlobalScale; + + if (!this.shadeEasings.TryGetValue(entry.Id, out var shadeEasing)) + { + shadeEasing = new InOutCubic(TimeSpan.FromMilliseconds(350)); + this.shadeEasings.Add(entry.Id, shadeEasing); + } + + var initialCursor = ImGui.GetCursorPos(); + + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)shadeEasing.Value); + ImGui.Image(this.shadeTexture.ImGuiHandle, new Vector2(this.shadeTexture.Width * scale, this.shadeTexture.Height * scale)); + ImGui.PopStyleVar(); + + var isHover = ImGui.IsItemHovered(); + if (isHover && (!shadeEasing.IsRunning || (shadeEasing.IsDone && shadeEasing.IsInverse)) && !inhibitFadeout) + { + shadeEasing.IsInverse = false; + shadeEasing.Restart(); + } + else if (!isHover && !shadeEasing.IsInverse && shadeEasing.IsRunning && !inhibitFadeout) + { + shadeEasing.IsInverse = true; + shadeEasing.Restart(); + } + + var isClick = ImGui.IsItemClicked(); + if (isClick) + { + entry.Trigger(); + } + + shadeEasing.Update(); + + if (!this.logoEasings.TryGetValue(entry.Id, out var logoEasing)) + { + logoEasing = new InOutCubic(TimeSpan.FromMilliseconds(350)); + this.logoEasings.Add(entry.Id, logoEasing); + } + + if (!logoEasing.IsRunning && !logoEasing.IsDone) + { + logoEasing.Restart(); + } + + if (logoEasing.IsDone) + { + logoEasing.Stop(); + } + + logoEasing.Update(); + + ImGui.SetCursorPos(initialCursor); + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + if (overrideAlpha) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.Value); + } + else if (isFirst) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f); + } + + ImGui.Image(entry.Texture.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale)); + if (overrideAlpha || isFirst) + { + ImGui.PopStyleVar(); + } + + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(10); + ImGui.SameLine(); + + var textHeight = ImGui.GetTextLineHeightWithSpacing(); + var cursor = ImGui.GetCursorPos(); + + cursor.Y += (entry.Texture.Height * scale / 2) - (textHeight / 2); + + if (overrideAlpha) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); + } + + // Drop shadow + ImGui.PushStyleColor(ImGuiCol.Text, 0xFF000000); + for (int i = 0, i_ = (int)Math.Ceiling(1 * scale); i < i_; i++) + { + ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i)); + ImGui.Text(entry.Name); + } + + ImGui.PopStyleColor(); + + ImGui.SetCursorPos(cursor); + ImGui.Text(entry.Name); + + if (overrideAlpha) + { + ImGui.PopStyleVar(); + } + + initialCursor.Y += entry.Texture.Height * scale; + ImGui.SetCursorPos(initialCursor); + + ImGui.PopFont(); + + return isHover; + } + + private void FrameworkOnUpdate(Framework framework) + { + var clientState = Service.Get(); + this.IsOpen = !clientState.IsLoggedIn; + + var configuration = Service.Get(); + if (!configuration.ShowTsm) + this.IsOpen = false; + + var gameGui = Service.Get(); + var charaSelect = gameGui.GetAddonByName("CharaSelect", 1); + var charaMake = gameGui.GetAddonByName("CharaMake", 1); + var titleDcWorldMap = gameGui.GetAddonByName("TitleDCWorldMap", 1); + if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero) + this.IsOpen = false; + } } diff --git a/Dalamud/Interface/Style/DalamudColors.cs b/Dalamud/Interface/Style/DalamudColors.cs index e74097936..aa3339c19 100644 --- a/Dalamud/Interface/Style/DalamudColors.cs +++ b/Dalamud/Interface/Style/DalamudColors.cs @@ -3,167 +3,165 @@ 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; } + + [JsonProperty("k")] + public Vector4? DalamudYellow { get; set; } + + [JsonProperty("l")] + public Vector4? DalamudViolet { get; set; } + + [JsonProperty("m")] + public Vector4? ParsedGrey { get; set; } + + [JsonProperty("n")] + public Vector4? ParsedGreen { get; set; } + + [JsonProperty("o")] + public Vector4? ParsedBlue { get; set; } + + [JsonProperty("p")] + public Vector4? ParsedPurple { get; set; } + + [JsonProperty("q")] + public Vector4? ParsedOrange { get; set; } + + [JsonProperty("r")] + public Vector4? ParsedPink { get; set; } + + [JsonProperty("s")] + public Vector4? ParsedGold { 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; } - - [JsonProperty("k")] - public Vector4? DalamudYellow { get; set; } - - [JsonProperty("l")] - public Vector4? DalamudViolet { get; set; } - - [JsonProperty("m")] - public Vector4? ParsedGrey { get; set; } - - [JsonProperty("n")] - public Vector4? ParsedGreen { get; set; } - - [JsonProperty("o")] - public Vector4? ParsedBlue { get; set; } - - [JsonProperty("p")] - public Vector4? ParsedPurple { get; set; } - - [JsonProperty("q")] - public Vector4? ParsedOrange { get; set; } - - [JsonProperty("r")] - public Vector4? ParsedPink { get; set; } - - [JsonProperty("s")] - public Vector4? ParsedGold { 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; - } - - if (this.DalamudYellow.HasValue) - { - ImGuiColors.DalamudYellow = this.DalamudYellow.Value; - } - - if (this.DalamudViolet.HasValue) - { - ImGuiColors.DalamudViolet = this.DalamudViolet.Value; - } - - if (this.ParsedGrey.HasValue) - { - ImGuiColors.ParsedGrey = this.ParsedGrey.Value; - } - - if (this.ParsedGreen.HasValue) - { - ImGuiColors.ParsedGreen = this.ParsedGreen.Value; - } - - if (this.ParsedBlue.HasValue) - { - ImGuiColors.ParsedBlue = this.ParsedBlue.Value; - } - - if (this.ParsedPurple.HasValue) - { - ImGuiColors.ParsedPurple = this.ParsedPurple.Value; - } - - if (this.ParsedOrange.HasValue) - { - ImGuiColors.ParsedOrange = this.ParsedOrange.Value; - } - - if (this.ParsedPink.HasValue) - { - ImGuiColors.ParsedPink = this.ParsedPink.Value; - } - - if (this.ParsedGold.HasValue) - { - ImGuiColors.ParsedGold = this.ParsedGold.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; + } + + if (this.DalamudYellow.HasValue) + { + ImGuiColors.DalamudYellow = this.DalamudYellow.Value; + } + + if (this.DalamudViolet.HasValue) + { + ImGuiColors.DalamudViolet = this.DalamudViolet.Value; + } + + if (this.ParsedGrey.HasValue) + { + ImGuiColors.ParsedGrey = this.ParsedGrey.Value; + } + + if (this.ParsedGreen.HasValue) + { + ImGuiColors.ParsedGreen = this.ParsedGreen.Value; + } + + if (this.ParsedBlue.HasValue) + { + ImGuiColors.ParsedBlue = this.ParsedBlue.Value; + } + + if (this.ParsedPurple.HasValue) + { + ImGuiColors.ParsedPurple = this.ParsedPurple.Value; + } + + if (this.ParsedOrange.HasValue) + { + ImGuiColors.ParsedOrange = this.ParsedOrange.Value; + } + + if (this.ParsedPink.HasValue) + { + ImGuiColors.ParsedPink = this.ParsedPink.Value; + } + + if (this.ParsedGold.HasValue) + { + ImGuiColors.ParsedGold = this.ParsedGold.Value; + } + } } + +#pragma warning restore SA1600 diff --git a/Dalamud/Interface/Style/StyleModel.cs b/Dalamud/Interface/Style/StyleModel.cs index 339ce52ea..89f5c39a6 100644 --- a/Dalamud/Interface/Style/StyleModel.cs +++ b/Dalamud/Interface/Style/StyleModel.cs @@ -10,179 +10,178 @@ using ImGuiNET; 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 { + private static int numPushedStyles = 0; + private static int numPushedColors = 0; + private static bool hasPushedOnce = false; + /// - /// 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() { - private static int numPushedStyles = 0; - private static int numPushedColors = 0; - private static bool hasPushedOnce = false; + var configuration = Service.Get(); + return configuration.SavedStyles?.FirstOrDefault(x => x.Name == configuration.ChosenStyle); + } - /// - /// Gets or sets the name of the style model. - /// - [JsonProperty("name")] - public string Name { get; set; } = "Unknown"; + /// + /// Get an enumerable of all saved styles. + /// + /// Enumerable of saved styles. + public static IEnumerable? GetConfiguredStyles() => Service.Get().SavedStyles; - /// - /// Gets or sets class representing Dalamud-builtin . - /// - [JsonProperty("dol")] - public DalamudColors? BuiltInColors { get; set; } + /// + /// 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))); - /// - /// Gets or sets version number of this model. - /// - [JsonProperty("ver")] - public int Version { get; set; } + if (model.StartsWith(StyleModelV1.SerializedPrefix)) + return JsonConvert.DeserializeObject(json); - /// - /// Get a StyleModel based on the current Dalamud style, with the current version. - /// - /// The current style. - public static StyleModel GetFromCurrent() => StyleModelV1.Get(); + throw new ArgumentException("Was not a compressed style model."); + } - /// - /// Get the current style model, as per configuration. - /// - /// The current style, as per configuration. - public static StyleModel? GetConfiguredStyle() + /// + /// [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 {NumStyles} 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) { - var configuration = Service.Get(); - return configuration.SavedStyles?.FirstOrDefault(x => x.Name == configuration.ChosenStyle); + case StyleModelV1: + prefix = StyleModelV1.SerializedPrefix; + break; + default: + throw new ArgumentOutOfRangeException(); } - /// - /// Get an enumerable of all saved styles. - /// - /// Enumerable of saved styles. - public static IEnumerable? GetConfiguredStyles() => Service.Get().SavedStyles; + return prefix + Convert.ToBase64String(Util.CompressString(JsonConvert.SerializeObject(this))); + } - /// - /// 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))); + /// + /// Apply this style model to ImGui. + /// + public abstract void Apply(); - if (model.StartsWith(StyleModelV1.SerializedPrefix)) - return JsonConvert.DeserializeObject(json); + /// + /// Push this StyleModel into the ImGui style/color stack. + /// + public abstract void Push(); - throw new ArgumentException("Was not a compressed style model."); - } + /// + /// Pop this style model from the ImGui style/color stack. + /// + public void Pop() + { + if (!hasPushedOnce) + throw new InvalidOperationException("Wasn't pushed at least once."); - /// - /// [TEMPORARY] Transfer old non-polymorphic style models to the new format. - /// - public static void TransferOldModels() - { - var configuration = Service.Get(); + ImGui.PopStyleVar(numPushedStyles); + ImGui.PopStyleColor(numPushedColors); + } - if (configuration.SavedStylesOld == null) - return; + /// + /// Push a style var. + /// + /// Style kind. + /// Style var. + protected void PushStyleHelper(ImGuiStyleVar style, float arg) + { + ImGui.PushStyleVar(style, arg); - configuration.SavedStyles = new List(); - configuration.SavedStyles.AddRange(configuration.SavedStylesOld); + if (!hasPushedOnce) + numPushedStyles++; + } - Log.Information("Transferred {NumStyles} styles", configuration.SavedStyles.Count); + /// + /// Push a style var. + /// + /// Style kind. + /// Style var. + protected void PushStyleHelper(ImGuiStyleVar style, Vector2 arg) + { + ImGui.PushStyleVar(style, arg); - configuration.SavedStylesOld = null; - configuration.Save(); - } + if (!hasPushedOnce) + numPushedStyles++; + } - /// - /// 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(); - } + /// + /// Push a style color. + /// + /// Color kind. + /// Color value. + protected void PushColorHelper(ImGuiCol color, Vector4 value) + { + ImGui.PushStyleColor(color, value); - return prefix + Convert.ToBase64String(Util.CompressString(JsonConvert.SerializeObject(this))); - } + if (!hasPushedOnce) + numPushedColors++; + } - /// - /// 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 void Pop() - { - if (!hasPushedOnce) - throw new InvalidOperationException("Wasn't pushed at least once."); - - ImGui.PopStyleVar(numPushedStyles); - ImGui.PopStyleColor(numPushedColors); - } - - /// - /// Push a style var. - /// - /// Style kind. - /// Style var. - protected void PushStyleHelper(ImGuiStyleVar style, float arg) - { - ImGui.PushStyleVar(style, arg); - - if (!hasPushedOnce) - numPushedStyles++; - } - - /// - /// Push a style var. - /// - /// Style kind. - /// Style var. - protected void PushStyleHelper(ImGuiStyleVar style, Vector2 arg) - { - ImGui.PushStyleVar(style, arg); - - if (!hasPushedOnce) - numPushedStyles++; - } - - /// - /// Push a style color. - /// - /// Color kind. - /// Color value. - protected void PushColorHelper(ImGuiCol color, Vector4 value) - { - ImGui.PushStyleColor(color, value); - - if (!hasPushedOnce) - numPushedColors++; - } - - /// - /// Indicate that you have pushed. - /// - protected void DonePushing() - { - hasPushedOnce = true; - } + /// + /// Indicate that you have pushed. + /// + protected void DonePushing() + { + hasPushedOnce = true; } } diff --git a/Dalamud/Interface/Style/StyleModelV1.cs b/Dalamud/Interface/Style/StyleModelV1.cs index 54aff8f84..ee6bb06a4 100644 --- a/Dalamud/Interface/Style/StyleModelV1.cs +++ b/Dalamud/Interface/Style/StyleModelV1.cs @@ -6,522 +6,521 @@ 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"; + } + + /// + /// 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, + PopupBorderSize = 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 { - this.Colors = new Dictionary(); - this.Name = "Unknown"; - } + { "Text", new Vector4(1, 1, 1, 1) }, + { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1) }, + { "WindowBg", new Vector4(0.06f, 0.06f, 0.06f, 0.93f) }, + { "ChildBg", new Vector4(0, 0, 0, 0) }, + { "PopupBg", new Vector4(0.08f, 0.08f, 0.08f, 0.94f) }, + { "Border", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, + { "BorderShadow", new Vector4(0, 0, 0, 0) }, + { "FrameBg", new Vector4(0.29f, 0.29f, 0.29f, 0.54f) }, + { "FrameBgHovered", new Vector4(0.54f, 0.54f, 0.54f, 0.4f) }, + { "FrameBgActive", new Vector4(0.64f, 0.64f, 0.64f, 0.67f) }, + { "TitleBg", new Vector4(0.022624433f, 0.022624206f, 0.022624206f, 0.85067874f) }, + { "TitleBgActive", new Vector4(0.38914025f, 0.10917056f, 0.10917056f, 0.8280543f) }, + { "TitleBgCollapsed", new Vector4(0, 0, 0, 0.51f) }, + { "MenuBarBg", new Vector4(0.14f, 0.14f, 0.14f, 1) }, + { "ScrollbarBg", new Vector4(0, 0, 0, 0) }, + { "ScrollbarGrab", new Vector4(0.31f, 0.31f, 0.31f, 1) }, + { "ScrollbarGrabHovered", new Vector4(0.41f, 0.41f, 0.41f, 1) }, + { "ScrollbarGrabActive", new Vector4(0.51f, 0.51f, 0.51f, 1) }, + { "CheckMark", new Vector4(0.86f, 0.86f, 0.86f, 1) }, + { "SliderGrab", new Vector4(0.54f, 0.54f, 0.54f, 1) }, + { "SliderGrabActive", new Vector4(0.67f, 0.67f, 0.67f, 1) }, + { "Button", new Vector4(0.71f, 0.71f, 0.71f, 0.4f) }, + { "ButtonHovered", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.94509804f) }, + { "ButtonActive", new Vector4(0.48416287f, 0.10077597f, 0.10077597f, 0.94509804f) }, + { "Header", new Vector4(0.59f, 0.59f, 0.59f, 0.31f) }, + { "HeaderHovered", new Vector4(0.5f, 0.5f, 0.5f, 0.8f) }, + { "HeaderActive", new Vector4(0.6f, 0.6f, 0.6f, 1) }, + { "Separator", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, + { "SeparatorHovered", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.78280544f) }, + { "SeparatorActive", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.94509804f) }, + { "ResizeGrip", new Vector4(0.79f, 0.79f, 0.79f, 0.25f) }, + { "ResizeGripHovered", new Vector4(0.78f, 0.78f, 0.78f, 0.67f) }, + { "ResizeGripActive", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.94509804f) }, + { "Tab", new Vector4(0.23f, 0.23f, 0.23f, 0.86f) }, + { "TabHovered", new Vector4(0.58371043f, 0.30374074f, 0.30374074f, 0.7647059f) }, + { "TabActive", new Vector4(0.47963798f, 0.15843244f, 0.15843244f, 0.7647059f) }, + { "TabUnfocused", new Vector4(0.068f, 0.10199998f, 0.14800003f, 0.9724f) }, + { "TabUnfocusedActive", new Vector4(0.13599998f, 0.26199996f, 0.424f, 1) }, + { "DockingPreview", new Vector4(0.26f, 0.59f, 0.98f, 0.7f) }, + { "DockingEmptyBg", new Vector4(0.2f, 0.2f, 0.2f, 1) }, + { "PlotLines", new Vector4(0.61f, 0.61f, 0.61f, 1) }, + { "PlotLinesHovered", new Vector4(1, 0.43f, 0.35f, 1) }, + { "PlotHistogram", new Vector4(0.9f, 0.7f, 0, 1) }, + { "PlotHistogramHovered", new Vector4(1, 0.6f, 0, 1) }, + { "TableHeaderBg", new Vector4(0.19f, 0.19f, 0.2f, 1) }, + { "TableBorderStrong", new Vector4(0.31f, 0.31f, 0.35f, 1) }, + { "TableBorderLight", new Vector4(0.23f, 0.23f, 0.25f, 1) }, + { "TableRowBg", new Vector4(0, 0, 0, 0) }, + { "TableRowBgAlt", new Vector4(1, 1, 1, 0.06f) }, + { "TextSelectedBg", new Vector4(0.26f, 0.59f, 0.98f, 0.35f) }, + { "DragDropTarget", new Vector4(1, 1, 0, 0.9f) }, + { "NavHighlight", new Vector4(0.26f, 0.59f, 0.98f, 1) }, + { "NavWindowingHighlight", new Vector4(1, 1, 1, 0.7f) }, + { "NavWindowingDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.2f) }, + { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, + }, - /// - /// Gets the standard Dalamud look. - /// - public static StyleModelV1 DalamudStandard => new() + BuiltInColors = new DalamudColors { - Name = "Dalamud Standard", + 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), + DalamudYellow = new Vector4(1f, 1f, .4f, 1f), + DalamudViolet = new Vector4(0.770f, 0.700f, 0.965f, 1.000f), + 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), + ParsedGrey = new Vector4(0.4f, 0.4f, 0.4f, 1f), + ParsedGreen = new Vector4(0.117f, 1f, 0f, 1f), + ParsedBlue = new Vector4(0f, 0.439f, 1f, 1f), + ParsedPurple = new Vector4(0.639f, 0.207f, 0.933f, 1f), + ParsedOrange = new Vector4(1f, 0.501f, 0f, 1f), + ParsedPink = new Vector4(0.886f, 0.407f, 0.658f, 1f), + ParsedGold = new Vector4(0.898f, 0.8f, 0.501f, 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, - PopupBorderSize = 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), + /// + /// Gets the standard Dalamud look. + /// + public static StyleModelV1 DalamudClassic => new() + { + Name = "Dalamud Classic", - Colors = new Dictionary - { - { "Text", new Vector4(1, 1, 1, 1) }, - { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1) }, - { "WindowBg", new Vector4(0.06f, 0.06f, 0.06f, 0.93f) }, - { "ChildBg", new Vector4(0, 0, 0, 0) }, - { "PopupBg", new Vector4(0.08f, 0.08f, 0.08f, 0.94f) }, - { "Border", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, - { "BorderShadow", new Vector4(0, 0, 0, 0) }, - { "FrameBg", new Vector4(0.29f, 0.29f, 0.29f, 0.54f) }, - { "FrameBgHovered", new Vector4(0.54f, 0.54f, 0.54f, 0.4f) }, - { "FrameBgActive", new Vector4(0.64f, 0.64f, 0.64f, 0.67f) }, - { "TitleBg", new Vector4(0.022624433f, 0.022624206f, 0.022624206f, 0.85067874f) }, - { "TitleBgActive", new Vector4(0.38914025f, 0.10917056f, 0.10917056f, 0.8280543f) }, - { "TitleBgCollapsed", new Vector4(0, 0, 0, 0.51f) }, - { "MenuBarBg", new Vector4(0.14f, 0.14f, 0.14f, 1) }, - { "ScrollbarBg", new Vector4(0, 0, 0, 0) }, - { "ScrollbarGrab", new Vector4(0.31f, 0.31f, 0.31f, 1) }, - { "ScrollbarGrabHovered", new Vector4(0.41f, 0.41f, 0.41f, 1) }, - { "ScrollbarGrabActive", new Vector4(0.51f, 0.51f, 0.51f, 1) }, - { "CheckMark", new Vector4(0.86f, 0.86f, 0.86f, 1) }, - { "SliderGrab", new Vector4(0.54f, 0.54f, 0.54f, 1) }, - { "SliderGrabActive", new Vector4(0.67f, 0.67f, 0.67f, 1) }, - { "Button", new Vector4(0.71f, 0.71f, 0.71f, 0.4f) }, - { "ButtonHovered", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.94509804f) }, - { "ButtonActive", new Vector4(0.48416287f, 0.10077597f, 0.10077597f, 0.94509804f) }, - { "Header", new Vector4(0.59f, 0.59f, 0.59f, 0.31f) }, - { "HeaderHovered", new Vector4(0.5f, 0.5f, 0.5f, 0.8f) }, - { "HeaderActive", new Vector4(0.6f, 0.6f, 0.6f, 1) }, - { "Separator", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, - { "SeparatorHovered", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.78280544f) }, - { "SeparatorActive", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.94509804f) }, - { "ResizeGrip", new Vector4(0.79f, 0.79f, 0.79f, 0.25f) }, - { "ResizeGripHovered", new Vector4(0.78f, 0.78f, 0.78f, 0.67f) }, - { "ResizeGripActive", new Vector4(0.3647059f, 0.078431375f, 0.078431375f, 0.94509804f) }, - { "Tab", new Vector4(0.23f, 0.23f, 0.23f, 0.86f) }, - { "TabHovered", new Vector4(0.58371043f, 0.30374074f, 0.30374074f, 0.7647059f) }, - { "TabActive", new Vector4(0.47963798f, 0.15843244f, 0.15843244f, 0.7647059f) }, - { "TabUnfocused", new Vector4(0.068f, 0.10199998f, 0.14800003f, 0.9724f) }, - { "TabUnfocusedActive", new Vector4(0.13599998f, 0.26199996f, 0.424f, 1) }, - { "DockingPreview", new Vector4(0.26f, 0.59f, 0.98f, 0.7f) }, - { "DockingEmptyBg", new Vector4(0.2f, 0.2f, 0.2f, 1) }, - { "PlotLines", new Vector4(0.61f, 0.61f, 0.61f, 1) }, - { "PlotLinesHovered", new Vector4(1, 0.43f, 0.35f, 1) }, - { "PlotHistogram", new Vector4(0.9f, 0.7f, 0, 1) }, - { "PlotHistogramHovered", new Vector4(1, 0.6f, 0, 1) }, - { "TableHeaderBg", new Vector4(0.19f, 0.19f, 0.2f, 1) }, - { "TableBorderStrong", new Vector4(0.31f, 0.31f, 0.35f, 1) }, - { "TableBorderLight", new Vector4(0.23f, 0.23f, 0.25f, 1) }, - { "TableRowBg", new Vector4(0, 0, 0, 0) }, - { "TableRowBgAlt", new Vector4(1, 1, 1, 0.06f) }, - { "TextSelectedBg", new Vector4(0.26f, 0.59f, 0.98f, 0.35f) }, - { "DragDropTarget", new Vector4(1, 1, 0, 0.9f) }, - { "NavHighlight", new Vector4(0.26f, 0.59f, 0.98f, 1) }, - { "NavWindowingHighlight", new Vector4(1, 1, 1, 0.7f) }, - { "NavWindowingDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.2f) }, - { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, - }, + 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, + PopupBorderSize = 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), - 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), - DalamudYellow = new Vector4(1f, 1f, .4f, 1f), - DalamudViolet = new Vector4(0.770f, 0.700f, 0.965f, 1.000f), - 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), - ParsedGrey = new Vector4(0.4f, 0.4f, 0.4f, 1f), - ParsedGreen = new Vector4(0.117f, 1f, 0f, 1f), - ParsedBlue = new Vector4(0f, 0.439f, 1f, 1f), - ParsedPurple = new Vector4(0.639f, 0.207f, 0.933f, 1f), - ParsedOrange = new Vector4(1f, 0.501f, 0f, 1f), - ParsedPink = new Vector4(0.886f, 0.407f, 0.658f, 1f), - ParsedGold = new Vector4(0.898f, 0.8f, 0.501f, 1f), - }, - }; - - /// - /// Gets the standard Dalamud look. - /// - public static StyleModelV1 DalamudClassic => new() + Colors = new Dictionary { - Name = "Dalamud Classic", + { "Text", new Vector4(1f, 1f, 1f, 1f) }, + { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1f) }, + { "WindowBg", new Vector4(0.06f, 0.06f, 0.06f, 0.87f) }, + { "ChildBg", new Vector4(0f, 0f, 0f, 0f) }, + { "PopupBg", new Vector4(0.08f, 0.08f, 0.08f, 0.94f) }, + { "Border", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, + { "BorderShadow", new Vector4(0f, 0f, 0f, 0f) }, + { "FrameBg", new Vector4(0.29f, 0.29f, 0.29f, 0.54f) }, + { "FrameBgHovered", new Vector4(0.54f, 0.54f, 0.54f, 0.4f) }, + { "FrameBgActive", new Vector4(0.64f, 0.64f, 0.64f, 0.67f) }, + { "TitleBg", new Vector4(0.04f, 0.04f, 0.04f, 1f) }, + { "TitleBgActive", new Vector4(0.29f, 0.29f, 0.29f, 1f) }, + { "TitleBgCollapsed", new Vector4(0f, 0f, 0f, 0.51f) }, + { "MenuBarBg", new Vector4(0.14f, 0.14f, 0.14f, 1f) }, + { "ScrollbarBg", new Vector4(0f, 0f, 0f, 0f) }, + { "ScrollbarGrab", new Vector4(0.31f, 0.31f, 0.31f, 1f) }, + { "ScrollbarGrabHovered", new Vector4(0.41f, 0.41f, 0.41f, 1f) }, + { "ScrollbarGrabActive", new Vector4(0.51f, 0.51f, 0.51f, 1f) }, + { "CheckMark", new Vector4(0.86f, 0.86f, 0.86f, 1f) }, + { "SliderGrab", new Vector4(0.54f, 0.54f, 0.54f, 1f) }, + { "SliderGrabActive", new Vector4(0.67f, 0.67f, 0.67f, 1f) }, + { "Button", new Vector4(0.71f, 0.71f, 0.71f, 0.4f) }, + { "ButtonHovered", new Vector4(0.47f, 0.47f, 0.47f, 1f) }, + { "ButtonActive", new Vector4(0.74f, 0.74f, 0.74f, 1f) }, + { "Header", new Vector4(0.59f, 0.59f, 0.59f, 0.31f) }, + { "HeaderHovered", new Vector4(0.5f, 0.5f, 0.5f, 0.8f) }, + { "HeaderActive", new Vector4(0.6f, 0.6f, 0.6f, 1f) }, + { "Separator", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, + { "SeparatorHovered", new Vector4(0.1f, 0.4f, 0.75f, 0.78f) }, + { "SeparatorActive", new Vector4(0.1f, 0.4f, 0.75f, 1f) }, + { "ResizeGrip", new Vector4(0.79f, 0.79f, 0.79f, 0.25f) }, + { "ResizeGripHovered", new Vector4(0.78f, 0.78f, 0.78f, 0.67f) }, + { "ResizeGripActive", new Vector4(0.88f, 0.88f, 0.88f, 0.95f) }, + { "Tab", new Vector4(0.23f, 0.23f, 0.23f, 0.86f) }, + { "TabHovered", new Vector4(0.71f, 0.71f, 0.71f, 0.8f) }, + { "TabActive", new Vector4(0.36f, 0.36f, 0.36f, 1f) }, + { "TabUnfocused", new Vector4(0.068f, 0.10199998f, 0.14800003f, 0.9724f) }, + { "TabUnfocusedActive", new Vector4(0.13599998f, 0.26199996f, 0.424f, 1f) }, + { "DockingPreview", new Vector4(0.26f, 0.59f, 0.98f, 0.7f) }, + { "DockingEmptyBg", new Vector4(0.2f, 0.2f, 0.2f, 1f) }, + { "PlotLines", new Vector4(0.61f, 0.61f, 0.61f, 1f) }, + { "PlotLinesHovered", new Vector4(1f, 0.43f, 0.35f, 1f) }, + { "PlotHistogram", new Vector4(0.9f, 0.7f, 0f, 1f) }, + { "PlotHistogramHovered", new Vector4(1f, 0.6f, 0f, 1f) }, + { "TableHeaderBg", new Vector4(0.19f, 0.19f, 0.2f, 1f) }, + { "TableBorderStrong", new Vector4(0.31f, 0.31f, 0.35f, 1f) }, + { "TableBorderLight", new Vector4(0.23f, 0.23f, 0.25f, 1f) }, + { "TableRowBg", new Vector4(0f, 0f, 0f, 0f) }, + { "TableRowBgAlt", new Vector4(1f, 1f, 1f, 0.06f) }, + { "TextSelectedBg", new Vector4(0.26f, 0.59f, 0.98f, 0.35f) }, + { "DragDropTarget", new Vector4(1f, 1f, 0f, 0.9f) }, + { "NavHighlight", new Vector4(0.26f, 0.59f, 0.98f, 1f) }, + { "NavWindowingHighlight", new Vector4(1f, 1f, 1f, 0.7f) }, + { "NavWindowingDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.2f) }, + { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, + }, - 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, - PopupBorderSize = 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), + 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), + DalamudYellow = new Vector4(1f, 1f, .4f, 1f), + DalamudViolet = new Vector4(0.770f, 0.700f, 0.965f, 1.000f), + 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), + ParsedGrey = new Vector4(0.4f, 0.4f, 0.4f, 1f), + ParsedGreen = new Vector4(0.117f, 1f, 0f, 1f), + ParsedBlue = new Vector4(0f, 0.439f, 1f, 1f), + ParsedPurple = new Vector4(0.639f, 0.207f, 0.933f, 1f), + ParsedOrange = new Vector4(1f, 0.501f, 0f, 1f), + ParsedPink = new Vector4(0.886f, 0.407f, 0.658f, 1f), + ParsedGold = new Vector4(0.898f, 0.8f, 0.501f, 1f), + }, + }; - Colors = new Dictionary - { - { "Text", new Vector4(1f, 1f, 1f, 1f) }, - { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1f) }, - { "WindowBg", new Vector4(0.06f, 0.06f, 0.06f, 0.87f) }, - { "ChildBg", new Vector4(0f, 0f, 0f, 0f) }, - { "PopupBg", new Vector4(0.08f, 0.08f, 0.08f, 0.94f) }, - { "Border", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, - { "BorderShadow", new Vector4(0f, 0f, 0f, 0f) }, - { "FrameBg", new Vector4(0.29f, 0.29f, 0.29f, 0.54f) }, - { "FrameBgHovered", new Vector4(0.54f, 0.54f, 0.54f, 0.4f) }, - { "FrameBgActive", new Vector4(0.64f, 0.64f, 0.64f, 0.67f) }, - { "TitleBg", new Vector4(0.04f, 0.04f, 0.04f, 1f) }, - { "TitleBgActive", new Vector4(0.29f, 0.29f, 0.29f, 1f) }, - { "TitleBgCollapsed", new Vector4(0f, 0f, 0f, 0.51f) }, - { "MenuBarBg", new Vector4(0.14f, 0.14f, 0.14f, 1f) }, - { "ScrollbarBg", new Vector4(0f, 0f, 0f, 0f) }, - { "ScrollbarGrab", new Vector4(0.31f, 0.31f, 0.31f, 1f) }, - { "ScrollbarGrabHovered", new Vector4(0.41f, 0.41f, 0.41f, 1f) }, - { "ScrollbarGrabActive", new Vector4(0.51f, 0.51f, 0.51f, 1f) }, - { "CheckMark", new Vector4(0.86f, 0.86f, 0.86f, 1f) }, - { "SliderGrab", new Vector4(0.54f, 0.54f, 0.54f, 1f) }, - { "SliderGrabActive", new Vector4(0.67f, 0.67f, 0.67f, 1f) }, - { "Button", new Vector4(0.71f, 0.71f, 0.71f, 0.4f) }, - { "ButtonHovered", new Vector4(0.47f, 0.47f, 0.47f, 1f) }, - { "ButtonActive", new Vector4(0.74f, 0.74f, 0.74f, 1f) }, - { "Header", new Vector4(0.59f, 0.59f, 0.59f, 0.31f) }, - { "HeaderHovered", new Vector4(0.5f, 0.5f, 0.5f, 0.8f) }, - { "HeaderActive", new Vector4(0.6f, 0.6f, 0.6f, 1f) }, - { "Separator", new Vector4(0.43f, 0.43f, 0.5f, 0.5f) }, - { "SeparatorHovered", new Vector4(0.1f, 0.4f, 0.75f, 0.78f) }, - { "SeparatorActive", new Vector4(0.1f, 0.4f, 0.75f, 1f) }, - { "ResizeGrip", new Vector4(0.79f, 0.79f, 0.79f, 0.25f) }, - { "ResizeGripHovered", new Vector4(0.78f, 0.78f, 0.78f, 0.67f) }, - { "ResizeGripActive", new Vector4(0.88f, 0.88f, 0.88f, 0.95f) }, - { "Tab", new Vector4(0.23f, 0.23f, 0.23f, 0.86f) }, - { "TabHovered", new Vector4(0.71f, 0.71f, 0.71f, 0.8f) }, - { "TabActive", new Vector4(0.36f, 0.36f, 0.36f, 1f) }, - { "TabUnfocused", new Vector4(0.068f, 0.10199998f, 0.14800003f, 0.9724f) }, - { "TabUnfocusedActive", new Vector4(0.13599998f, 0.26199996f, 0.424f, 1f) }, - { "DockingPreview", new Vector4(0.26f, 0.59f, 0.98f, 0.7f) }, - { "DockingEmptyBg", new Vector4(0.2f, 0.2f, 0.2f, 1f) }, - { "PlotLines", new Vector4(0.61f, 0.61f, 0.61f, 1f) }, - { "PlotLinesHovered", new Vector4(1f, 0.43f, 0.35f, 1f) }, - { "PlotHistogram", new Vector4(0.9f, 0.7f, 0f, 1f) }, - { "PlotHistogramHovered", new Vector4(1f, 0.6f, 0f, 1f) }, - { "TableHeaderBg", new Vector4(0.19f, 0.19f, 0.2f, 1f) }, - { "TableBorderStrong", new Vector4(0.31f, 0.31f, 0.35f, 1f) }, - { "TableBorderLight", new Vector4(0.23f, 0.23f, 0.25f, 1f) }, - { "TableRowBg", new Vector4(0f, 0f, 0f, 0f) }, - { "TableRowBgAlt", new Vector4(1f, 1f, 1f, 0.06f) }, - { "TextSelectedBg", new Vector4(0.26f, 0.59f, 0.98f, 0.35f) }, - { "DragDropTarget", new Vector4(1f, 1f, 0f, 0.9f) }, - { "NavHighlight", new Vector4(0.26f, 0.59f, 0.98f, 1f) }, - { "NavWindowingHighlight", new Vector4(1f, 1f, 1f, 0.7f) }, - { "NavWindowingDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.2f) }, - { "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), - DalamudYellow = new Vector4(1f, 1f, .4f, 1f), - DalamudViolet = new Vector4(0.770f, 0.700f, 0.965f, 1.000f), - 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), - ParsedGrey = new Vector4(0.4f, 0.4f, 0.4f, 1f), - ParsedGreen = new Vector4(0.117f, 1f, 0f, 1f), - ParsedBlue = new Vector4(0f, 0.439f, 1f, 1f), - ParsedPurple = new Vector4(0.639f, 0.207f, 0.933f, 1f), - ParsedOrange = new Vector4(1f, 0.501f, 0f, 1f), - ParsedPink = new Vector4(0.886f, 0.407f, 0.658f, 1f), - ParsedGold = new Vector4(0.898f, 0.8f, 0.501f, 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("ab")] - public float PopupBorderSize { get; set; } + [JsonProperty("ab")] + public float PopupBorderSize { 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.PopupBorderSize = style.PopupBorderSize; + 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.PopupBorderSize = style.PopupBorderSize; - 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, - DalamudYellow = ImGuiColors.DalamudYellow, - DalamudViolet = ImGuiColors.DalamudViolet, - TankBlue = ImGuiColors.TankBlue, - HealerGreen = ImGuiColors.HealerGreen, - DPSRed = ImGuiColors.DPSRed, - ParsedGrey = ImGuiColors.ParsedGrey, - ParsedGreen = ImGuiColors.ParsedGreen, - ParsedBlue = ImGuiColors.ParsedBlue, - ParsedPurple = ImGuiColors.ParsedPurple, - ParsedOrange = ImGuiColors.ParsedOrange, - ParsedPink = ImGuiColors.ParsedPink, - ParsedGold = ImGuiColors.ParsedGold, - }; - - 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, + DalamudYellow = ImGuiColors.DalamudYellow, + DalamudViolet = ImGuiColors.DalamudViolet, + TankBlue = ImGuiColors.TankBlue, + HealerGreen = ImGuiColors.HealerGreen, + DPSRed = ImGuiColors.DPSRed, + ParsedGrey = ImGuiColors.ParsedGrey, + ParsedGreen = ImGuiColors.ParsedGreen, + ParsedBlue = ImGuiColors.ParsedBlue, + ParsedPurple = ImGuiColors.ParsedPurple, + ParsedOrange = ImGuiColors.ParsedOrange, + ParsedPink = ImGuiColors.ParsedPink, + ParsedGold = ImGuiColors.ParsedGold, + }; - 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.PopupBorderSize = this.PopupBorderSize; - 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.PopupBorderSize = this.PopupBorderSize; + 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() + this.BuiltInColors?.Apply(); + } + + /// + public override void Push() + { + this.PushStyleHelper(ImGuiStyleVar.Alpha, this.Alpha); + this.PushStyleHelper(ImGuiStyleVar.WindowPadding, this.WindowPadding); + this.PushStyleHelper(ImGuiStyleVar.WindowRounding, this.WindowRounding); + this.PushStyleHelper(ImGuiStyleVar.WindowBorderSize, this.WindowBorderSize); + this.PushStyleHelper(ImGuiStyleVar.WindowTitleAlign, this.WindowTitleAlign); + this.PushStyleHelper(ImGuiStyleVar.ChildRounding, this.ChildRounding); + this.PushStyleHelper(ImGuiStyleVar.ChildBorderSize, this.ChildBorderSize); + this.PushStyleHelper(ImGuiStyleVar.PopupRounding, this.PopupRounding); + this.PushStyleHelper(ImGuiStyleVar.PopupBorderSize, this.PopupBorderSize); + this.PushStyleHelper(ImGuiStyleVar.FramePadding, this.FramePadding); + this.PushStyleHelper(ImGuiStyleVar.FrameRounding, this.FrameRounding); + this.PushStyleHelper(ImGuiStyleVar.FrameBorderSize, this.FrameBorderSize); + this.PushStyleHelper(ImGuiStyleVar.ItemSpacing, this.ItemSpacing); + this.PushStyleHelper(ImGuiStyleVar.ItemInnerSpacing, this.ItemInnerSpacing); + this.PushStyleHelper(ImGuiStyleVar.CellPadding, this.CellPadding); + this.PushStyleHelper(ImGuiStyleVar.IndentSpacing, this.IndentSpacing); + this.PushStyleHelper(ImGuiStyleVar.ScrollbarSize, this.ScrollbarSize); + this.PushStyleHelper(ImGuiStyleVar.ScrollbarRounding, this.ScrollbarRounding); + this.PushStyleHelper(ImGuiStyleVar.GrabMinSize, this.GrabMinSize); + this.PushStyleHelper(ImGuiStyleVar.GrabRounding, this.GrabRounding); + this.PushStyleHelper(ImGuiStyleVar.TabRounding, this.TabRounding); + this.PushStyleHelper(ImGuiStyleVar.ButtonTextAlign, this.ButtonTextAlign); + this.PushStyleHelper(ImGuiStyleVar.SelectableTextAlign, this.SelectableTextAlign); + + foreach (var imGuiCol in Enum.GetValues()) { - this.PushStyleHelper(ImGuiStyleVar.Alpha, this.Alpha); - this.PushStyleHelper(ImGuiStyleVar.WindowPadding, this.WindowPadding); - this.PushStyleHelper(ImGuiStyleVar.WindowRounding, this.WindowRounding); - this.PushStyleHelper(ImGuiStyleVar.WindowBorderSize, this.WindowBorderSize); - this.PushStyleHelper(ImGuiStyleVar.WindowTitleAlign, this.WindowTitleAlign); - this.PushStyleHelper(ImGuiStyleVar.ChildRounding, this.ChildRounding); - this.PushStyleHelper(ImGuiStyleVar.ChildBorderSize, this.ChildBorderSize); - this.PushStyleHelper(ImGuiStyleVar.PopupRounding, this.PopupRounding); - this.PushStyleHelper(ImGuiStyleVar.PopupBorderSize, this.PopupBorderSize); - this.PushStyleHelper(ImGuiStyleVar.FramePadding, this.FramePadding); - this.PushStyleHelper(ImGuiStyleVar.FrameRounding, this.FrameRounding); - this.PushStyleHelper(ImGuiStyleVar.FrameBorderSize, this.FrameBorderSize); - this.PushStyleHelper(ImGuiStyleVar.ItemSpacing, this.ItemSpacing); - this.PushStyleHelper(ImGuiStyleVar.ItemInnerSpacing, this.ItemInnerSpacing); - this.PushStyleHelper(ImGuiStyleVar.CellPadding, this.CellPadding); - this.PushStyleHelper(ImGuiStyleVar.IndentSpacing, this.IndentSpacing); - this.PushStyleHelper(ImGuiStyleVar.ScrollbarSize, this.ScrollbarSize); - this.PushStyleHelper(ImGuiStyleVar.ScrollbarRounding, this.ScrollbarRounding); - this.PushStyleHelper(ImGuiStyleVar.GrabMinSize, this.GrabMinSize); - this.PushStyleHelper(ImGuiStyleVar.GrabRounding, this.GrabRounding); - this.PushStyleHelper(ImGuiStyleVar.TabRounding, this.TabRounding); - this.PushStyleHelper(ImGuiStyleVar.ButtonTextAlign, this.ButtonTextAlign); - this.PushStyleHelper(ImGuiStyleVar.SelectableTextAlign, this.SelectableTextAlign); - - foreach (var imGuiCol in Enum.GetValues()) + if (imGuiCol == ImGuiCol.COUNT) { - if (imGuiCol == ImGuiCol.COUNT) - { - continue; - } - - this.PushColorHelper(imGuiCol, this.Colors[imGuiCol.ToString()]); + continue; } - this.DonePushing(); + this.PushColorHelper(imGuiCol, this.Colors[imGuiCol.ToString()]); } + + this.DonePushing(); } } diff --git a/Dalamud/Interface/TitleScreenMenu.cs b/Dalamud/Interface/TitleScreenMenu.cs index 60ab5253e..7b3897fdb 100644 --- a/Dalamud/Interface/TitleScreenMenu.cs +++ b/Dalamud/Interface/TitleScreenMenu.cs @@ -7,235 +7,234 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using ImGuiScene; -namespace Dalamud.Interface +namespace Dalamud.Interface; + +/// +/// Class responsible for managing elements in the title screen menu. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[ServiceManager.BlockingEarlyLoadedService] +public class TitleScreenMenu : IServiceType { /// - /// Class responsible for managing elements in the title screen menu. + /// Gets the texture size needed for title screen menu logos. /// - [PluginInterface] - [InterfaceVersion("1.0")] - [ServiceManager.BlockingEarlyLoadedService] - public class TitleScreenMenu : IServiceType + internal const uint TextureSize = 64; + + private readonly List entries = new(); + + [ServiceManager.ServiceConstructor] + private TitleScreenMenu() { - /// - /// Gets the texture size needed for title screen menu logos. - /// - internal const uint TextureSize = 64; + } - private readonly List entries = new(); + /// + /// Gets the list of entries in the title screen menu. + /// + public IReadOnlyList Entries => this.entries; - [ServiceManager.ServiceConstructor] - private TitleScreenMenu() + /// + /// Adds a new entry to the title screen menu. + /// + /// The text to show. + /// The texture to show. + /// The action to execute when the option is selected. + /// A object that can be used to manage the entry. + /// Thrown when the texture provided does not match the required resolution(64x64). + public TitleScreenMenuEntry AddEntry(string text, TextureWrap texture, Action onTriggered) + { + if (texture.Height != TextureSize || texture.Width != TextureSize) { + throw new ArgumentException("Texture must be 64x64"); } - /// - /// Gets the list of entries in the title screen menu. - /// - public IReadOnlyList Entries => this.entries; + lock (this.entries) + { + var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == Assembly.GetCallingAssembly()).ToList(); + var priority = entriesOfAssembly.Any() + ? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1) + : 0; + var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered); + var i = this.entries.BinarySearch(entry); + if (i < 0) + i = ~i; + this.entries.Insert(i, entry); + return entry; + } + } + + /// + /// Adds a new entry to the title screen menu. + /// + /// Priority of the entry. + /// The text to show. + /// The texture to show. + /// The action to execute when the option is selected. + /// A object that can be used to manage the entry. + /// Thrown when the texture provided does not match the required resolution(64x64). + public TitleScreenMenuEntry AddEntry(ulong priority, string text, TextureWrap texture, Action onTriggered) + { + if (texture.Height != TextureSize || texture.Width != TextureSize) + { + throw new ArgumentException("Texture must be 64x64"); + } + + lock (this.entries) + { + var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered); + var i = this.entries.BinarySearch(entry); + if (i < 0) + i = ~i; + this.entries.Insert(i, entry); + return entry; + } + } + + /// + /// Remove an entry from the title screen menu. + /// + /// The entry to remove. + public void RemoveEntry(TitleScreenMenuEntry entry) + { + lock (this.entries) + { + this.entries.Remove(entry); + } + } + + /// + /// Adds a new entry to the title screen menu. + /// + /// Priority of the entry. + /// The text to show. + /// The texture to show. + /// The action to execute when the option is selected. + /// A object that can be used to manage the entry. + /// Thrown when the texture provided does not match the required resolution(64x64). + internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, TextureWrap texture, Action onTriggered) + { + if (texture.Height != TextureSize || texture.Width != TextureSize) + { + throw new ArgumentException("Texture must be 64x64"); + } + + lock (this.entries) + { + var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered); + this.entries.Add(entry); + return entry; + } + } + + /// + /// Adds a new entry to the title screen menu. + /// + /// The text to show. + /// The texture to show. + /// The action to execute when the option is selected. + /// A object that can be used to manage the entry. + /// Thrown when the texture provided does not match the required resolution(64x64). + internal TitleScreenMenuEntry AddEntryCore(string text, TextureWrap texture, Action onTriggered) + { + if (texture.Height != TextureSize || texture.Width != TextureSize) + { + throw new ArgumentException("Texture must be 64x64"); + } + + lock (this.entries) + { + var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == null).ToList(); + var priority = entriesOfAssembly.Any() + ? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1) + : 0; + var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered); + this.entries.Add(entry); + return entry; + } + } + + /// + /// Class representing an entry in the title screen menu. + /// + public class TitleScreenMenuEntry : IComparable + { + private readonly Action onTriggered; /// - /// Adds a new entry to the title screen menu. + /// Initializes a new instance of the class. /// + /// The calling assembly. + /// The priority of this entry. /// The text to show. /// The texture to show. /// The action to execute when the option is selected. - /// A object that can be used to manage the entry. - /// Thrown when the texture provided does not match the required resolution(64x64). - public TitleScreenMenuEntry AddEntry(string text, TextureWrap texture, Action onTriggered) + internal TitleScreenMenuEntry(Assembly? callingAssembly, ulong priority, string text, TextureWrap texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - - lock (this.entries) - { - var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == Assembly.GetCallingAssembly()).ToList(); - var priority = entriesOfAssembly.Any() - ? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1) - : 0; - var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered); - var i = this.entries.BinarySearch(entry); - if (i < 0) - i = ~i; - this.entries.Insert(i, entry); - return entry; - } + this.CallingAssembly = callingAssembly; + this.Priority = priority; + this.Name = text; + this.Texture = texture; + this.onTriggered = onTriggered; } /// - /// Adds a new entry to the title screen menu. + /// Gets the priority of this entry. /// - /// Priority of the entry. - /// The text to show. - /// The texture to show. - /// The action to execute when the option is selected. - /// A object that can be used to manage the entry. - /// Thrown when the texture provided does not match the required resolution(64x64). - public TitleScreenMenuEntry AddEntry(ulong priority, string text, TextureWrap texture, Action onTriggered) - { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - - lock (this.entries) - { - var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered); - var i = this.entries.BinarySearch(entry); - if (i < 0) - i = ~i; - this.entries.Insert(i, entry); - return entry; - } - } + public ulong Priority { get; init; } /// - /// Remove an entry from the title screen menu. + /// Gets or sets the name of this entry. /// - /// The entry to remove. - public void RemoveEntry(TitleScreenMenuEntry entry) - { - lock (this.entries) - { - this.entries.Remove(entry); - } - } + public string Name { get; set; } /// - /// Adds a new entry to the title screen menu. + /// Gets or sets the texture of this entry. /// - /// Priority of the entry. - /// The text to show. - /// The texture to show. - /// The action to execute when the option is selected. - /// A object that can be used to manage the entry. - /// Thrown when the texture provided does not match the required resolution(64x64). - internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, TextureWrap texture, Action onTriggered) - { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - - lock (this.entries) - { - var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered); - this.entries.Add(entry); - return entry; - } - } + public TextureWrap Texture { get; set; } /// - /// Adds a new entry to the title screen menu. + /// Gets the calling assembly of this entry. /// - /// The text to show. - /// The texture to show. - /// The action to execute when the option is selected. - /// A object that can be used to manage the entry. - /// Thrown when the texture provided does not match the required resolution(64x64). - internal TitleScreenMenuEntry AddEntryCore(string text, TextureWrap texture, Action onTriggered) - { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - - lock (this.entries) - { - var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == null).ToList(); - var priority = entriesOfAssembly.Any() - ? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1) - : 0; - var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered); - this.entries.Add(entry); - return entry; - } - } + internal Assembly? CallingAssembly { get; init; } /// - /// Class representing an entry in the title screen menu. + /// Gets the internal ID of this entry. /// - public class TitleScreenMenuEntry : IComparable + internal Guid Id { get; init; } = Guid.NewGuid(); + + /// + public int CompareTo(TitleScreenMenuEntry? other) { - private readonly Action onTriggered; - - /// - /// Initializes a new instance of the class. - /// - /// The calling assembly. - /// The priority of this entry. - /// The text to show. - /// The texture to show. - /// The action to execute when the option is selected. - internal TitleScreenMenuEntry(Assembly? callingAssembly, ulong priority, string text, TextureWrap texture, Action onTriggered) + if (other == null) + return 1; + if (this.CallingAssembly != other.CallingAssembly) { - this.CallingAssembly = callingAssembly; - this.Priority = priority; - this.Name = text; - this.Texture = texture; - this.onTriggered = onTriggered; - } - - /// - /// Gets the priority of this entry. - /// - public ulong Priority { get; init; } - - /// - /// Gets or sets the name of this entry. - /// - public string Name { get; set; } - - /// - /// Gets or sets the texture of this entry. - /// - public TextureWrap Texture { get; set; } - - /// - /// Gets the calling assembly of this entry. - /// - internal Assembly? CallingAssembly { get; init; } - - /// - /// Gets the internal ID of this entry. - /// - internal Guid Id { get; init; } = Guid.NewGuid(); - - /// - public int CompareTo(TitleScreenMenuEntry? other) - { - if (other == null) + if (this.CallingAssembly == null && other.CallingAssembly == null) + return 0; + if (this.CallingAssembly == null && other.CallingAssembly != null) + return -1; + if (this.CallingAssembly != null && other.CallingAssembly == null) return 1; - if (this.CallingAssembly != other.CallingAssembly) - { - if (this.CallingAssembly == null && other.CallingAssembly == null) - return 0; - if (this.CallingAssembly == null && other.CallingAssembly != null) - return -1; - if (this.CallingAssembly != null && other.CallingAssembly == null) - return 1; - return string.Compare( - this.CallingAssembly!.FullName!, - other.CallingAssembly!.FullName!, - StringComparison.CurrentCultureIgnoreCase); - } - - if (this.Priority != other.Priority) - return this.Priority.CompareTo(other.Priority); - if (this.Name != other.Name) - return string.Compare(this.Name, other.Name, StringComparison.InvariantCultureIgnoreCase); - return string.Compare(this.Name, other.Name, StringComparison.InvariantCulture); + return string.Compare( + this.CallingAssembly!.FullName!, + other.CallingAssembly!.FullName!, + StringComparison.CurrentCultureIgnoreCase); } - /// - /// Trigger the action associated with this entry. - /// - internal void Trigger() - { - this.onTriggered(); - } + if (this.Priority != other.Priority) + return this.Priority.CompareTo(other.Priority); + if (this.Name != other.Name) + return string.Compare(this.Name, other.Name, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(this.Name, other.Name, StringComparison.InvariantCulture); + } + + /// + /// Trigger the action associated with this entry. + /// + internal void Trigger() + { + this.onTriggered(); } } } diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 73a5c1f79..e0818337b 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -17,505 +17,504 @@ 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 readonly InterfaceManager interfaceManager = Service.Get(); + private readonly GameFontManager gameFontManager = Service.Get(); + + private bool hasErrorWindow = false; + private bool lastFrameUiHideState = false; + /// - /// 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; - private readonly InterfaceManager interfaceManager = Service.Get(); - private readonly GameFontManager gameFontManager = Service.Get(); + this.stopwatch = new Stopwatch(); + this.namespaceName = namespaceName; - private bool hasErrorWindow = false; - private bool lastFrameUiHideState = false; + this.interfaceManager.Draw += this.OnDraw; + this.interfaceManager.BuildFonts += this.OnBuildFonts; + this.interfaceManager.AfterBuildFonts += this.OnAfterBuildFonts; + this.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) + /// + /// 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; + + /// + /// The event that is called when the game's DirectX device is requesting you to resize your buffers. + /// + public event Action ResizeBuffers; + + /// + /// Event that is fired when the plugin should open its configuration interface. + /// + public event Action OpenConfigUi; + + /// + /// 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 or sets an action that is called any time right after ImGui fonts are 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 AfterBuildFonts; + + /// + /// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be hidden. + /// These may be fired consecutively. + /// + public event Action ShowUi; + + /// + /// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be shown. + /// These may be fired consecutively. + /// + public event Action HideUi; + + /// + /// 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 icon font based on FontAwesome 5 Free solid in 17pt. + /// + public static ImFontPtr IconFont => InterfaceManager.IconFont; + + /// + /// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt. + /// + public static ImFontPtr MonoFont => InterfaceManager.MonoFont; + + /// + /// Gets the game's active Direct3D device. + /// + public Device Device => this.InterfaceManagerWithScene.Device!; + + /// + /// Gets the game's main window handle. + /// + public IntPtr WindowHandlePtr => this.InterfaceManagerWithScene.WindowHandlePtr; + + /// + /// 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 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 during cutscenes. + /// + public bool DisableCutsceneUiHide { 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 or not the game's cursor should be overridden with the ImGui cursor. + /// + public bool OverrideGameCursor + { + get => this.interfaceManager.OverrideGameCursor; + set => this.interfaceManager.OverrideGameCursor = value; + } + + /// + /// Gets the count of Draw calls made since plugin creation. + /// + public ulong FrameCount { get; private set; } = 0; + + /// + /// Gets a value indicating whether or not a cutscene is playing. + /// + public bool CutsceneActive + { + get { - this.stopwatch = new Stopwatch(); - this.namespaceName = namespaceName; - - this.interfaceManager.Draw += this.OnDraw; - this.interfaceManager.BuildFonts += this.OnBuildFonts; - this.interfaceManager.AfterBuildFonts += this.OnAfterBuildFonts; - this.interfaceManager.ResizeBuffers += this.OnResizeBuffers; + var condition = Service.GetNullable(); + if (condition == null) + return false; + return condition[ConditionFlag.OccupiedInCutSceneEvent] + || condition[ConditionFlag.WatchingCutscene78]; } + } - /// - /// 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; - - /// - /// The event that is called when the game's DirectX device is requesting you to resize your buffers. - /// - public event Action ResizeBuffers; - - /// - /// Event that is fired when the plugin should open its configuration interface. - /// - public event Action OpenConfigUi; - - /// - /// 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 or sets an action that is called any time right after ImGui fonts are 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 AfterBuildFonts; - - /// - /// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be hidden. - /// These may be fired consecutively. - /// - public event Action ShowUi; - - /// - /// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be shown. - /// These may be fired consecutively. - /// - public event Action HideUi; - - /// - /// 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 icon font based on FontAwesome 5 Free solid in 17pt. - /// - public static ImFontPtr IconFont => InterfaceManager.IconFont; - - /// - /// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt. - /// - public static ImFontPtr MonoFont => InterfaceManager.MonoFont; - - /// - /// Gets the game's active Direct3D device. - /// - public Device Device => this.InterfaceManagerWithScene.Device!; - - /// - /// Gets the game's main window handle. - /// - public IntPtr WindowHandlePtr => this.InterfaceManagerWithScene.WindowHandlePtr; - - /// - /// 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 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 during cutscenes. - /// - public bool DisableCutsceneUiHide { 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 or not the game's cursor should be overridden with the ImGui cursor. - /// - public bool OverrideGameCursor + /// + /// Gets a value indicating whether or not gpose is active. + /// + public bool GposeActive + { + get { - get => this.interfaceManager.OverrideGameCursor; - set => this.interfaceManager.OverrideGameCursor = value; + var condition = Service.GetNullable(); + if (condition == null) + return false; + return condition[ConditionFlag.WatchingCutscene]; } + } - /// - /// Gets the count of Draw calls made since plugin creation. - /// - public ulong FrameCount { get; private set; } = 0; + /// + /// Gets a value indicating whether this plugin should modify the game's interface at this time. + /// + public bool ShouldModifyUi => this.interfaceManager.IsDispatchingEvents; - /// - /// Gets a value indicating whether or not a cutscene is playing. - /// - public bool CutsceneActive - { - get - { - var condition = Service.GetNullable(); - if (condition == null) - return false; - return condition[ConditionFlag.OccupiedInCutSceneEvent] - || condition[ConditionFlag.WatchingCutscene78]; - } - } + /// + /// Gets a value indicating whether UI functions can be used. + /// + public bool UiPrepared => Service.GetNullable() != null; - /// - /// Gets a value indicating whether or not gpose is active. - /// - public bool GposeActive - { - get - { - var condition = Service.GetNullable(); - if (condition == null) - return false; - return condition[ConditionFlag.WatchingCutscene]; - } - } - - /// - /// Gets a value indicating whether this plugin should modify the game's interface at this time. - /// - public bool ShouldModifyUi => this.interfaceManager.IsDispatchingEvents; - - /// - /// Gets a value indicating whether UI functions can be used. - /// - public bool UiPrepared => Service.GetNullable() != null; - - /// - /// 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; #else - internal static bool DoStats { get; set; } = false; + 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 InterfaceManager? InterfaceManagerWithScene => - Service.GetNullable()?.Manager; + private InterfaceManager? InterfaceManagerWithScene => + Service.GetNullable()?.Manager; - private Task InterfaceManagerWithSceneAsync => - Service.GetAsync().ContinueWith(task => task.Result.Manager); + private Task InterfaceManagerWithSceneAsync => + Service.GetAsync().ContinueWith(task => task.Result.Manager); - /// - /// 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) - => this.InterfaceManagerWithScene?.LoadImage(filePath) - ?? throw new InvalidOperationException("Load failed."); + /// + /// 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) + => this.InterfaceManagerWithScene?.LoadImage(filePath) + ?? throw new InvalidOperationException("Load failed."); - /// - /// 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) - => this.InterfaceManagerWithScene?.LoadImage(imageData) - ?? throw new InvalidOperationException("Load failed."); + /// + /// 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) + => this.InterfaceManagerWithScene?.LoadImage(imageData) + ?? throw new InvalidOperationException("Load failed."); - /// - /// 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) - => this.InterfaceManagerWithScene?.LoadImageRaw(imageData, width, height, numChannels) - ?? throw new InvalidOperationException("Load failed."); + /// + /// 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) + => this.InterfaceManagerWithScene?.LoadImageRaw(imageData, width, height, numChannels) + ?? throw new InvalidOperationException("Load failed."); - /// - /// Asynchronously loads an image from the specified file, when it's possible to do so. - /// - /// The full filepath to the image. - /// A object wrapping the created image. Use inside ImGui.Image(). - public Task LoadImageAsync(string filePath) => Task.Run( - async () => - (await this.InterfaceManagerWithSceneAsync).LoadImage(filePath) - ?? throw new InvalidOperationException("Load failed.")); + /// + /// Asynchronously loads an image from the specified file, when it's possible to do so. + /// + /// The full filepath to the image. + /// A object wrapping the created image. Use inside ImGui.Image(). + public Task LoadImageAsync(string filePath) => Task.Run( + async () => + (await this.InterfaceManagerWithSceneAsync).LoadImage(filePath) + ?? throw new InvalidOperationException("Load failed.")); - /// - /// Asynchronously loads an image from a byte stream, such as a png downloaded into memory, when it's possible to do so. - /// - /// A byte array containing the raw image data. - /// A object wrapping the created image. Use inside ImGui.Image(). - public Task LoadImageAsync(byte[] imageData) => Task.Run( - async () => - (await this.InterfaceManagerWithSceneAsync).LoadImage(imageData) - ?? throw new InvalidOperationException("Load failed.")); + /// + /// Asynchronously loads an image from a byte stream, such as a png downloaded into memory, when it's possible to do so. + /// + /// A byte array containing the raw image data. + /// A object wrapping the created image. Use inside ImGui.Image(). + public Task LoadImageAsync(byte[] imageData) => Task.Run( + async () => + (await this.InterfaceManagerWithSceneAsync).LoadImage(imageData) + ?? throw new InvalidOperationException("Load failed.")); - /// - /// Asynchronously loads an image from raw unformatted pixel data, with no type or header information, when it's possible to do so. 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 Task LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) => Task.Run( - async () => - (await this.InterfaceManagerWithSceneAsync).LoadImageRaw(imageData, width, height, numChannels) - ?? throw new InvalidOperationException("Load failed.")); + /// + /// Asynchronously loads an image from raw unformatted pixel data, with no type or header information, when it's possible to do so. 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 Task LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) => Task.Run( + async () => + (await this.InterfaceManagerWithSceneAsync).LoadImageRaw(imageData, width, height, numChannels) + ?? throw new InvalidOperationException("Load failed.")); - /// - /// Waits for UI to become available for use. - /// - /// A task that completes when the game's Present has been called at least once. - public Task WaitForUi() => this.InterfaceManagerWithSceneAsync; + /// + /// Waits for UI to become available for use. + /// + /// A task that completes when the game's Present has been called at least once. + public Task WaitForUi() => this.InterfaceManagerWithSceneAsync; - /// - /// Waits for UI to become available for use. - /// - /// Function to call. - /// Specifies whether to call the function from the framework thread. - /// A task that completes when the game's Present has been called at least once. - /// Return type. - public Task RunWhenUiPrepared(Func func, bool runInFrameworkThread = false) + /// + /// Waits for UI to become available for use. + /// + /// Function to call. + /// Specifies whether to call the function from the framework thread. + /// A task that completes when the game's Present has been called at least once. + /// Return type. + public Task RunWhenUiPrepared(Func func, bool runInFrameworkThread = false) + { + if (runInFrameworkThread) { - if (runInFrameworkThread) + return this.InterfaceManagerWithSceneAsync + .ContinueWith(_ => Service.Get().RunOnFrameworkThread(func)) + .Unwrap(); + } + else + { + return this.InterfaceManagerWithSceneAsync + .ContinueWith(_ => func()); + } + } + + /// + /// Waits for UI to become available for use. + /// + /// Function to call. + /// Specifies whether to call the function from the framework thread. + /// A task that completes when the game's Present has been called at least once. + /// Return type. + public Task RunWhenUiPrepared(Func> func, bool runInFrameworkThread = false) + { + if (runInFrameworkThread) + { + return this.InterfaceManagerWithSceneAsync + .ContinueWith(_ => Service.Get().RunOnFrameworkThread(func)) + .Unwrap(); + } + else + { + return this.InterfaceManagerWithSceneAsync + .ContinueWith(_ => func()) + .Unwrap(); + } + } + + /// + /// Gets a game font. + /// + /// Font to get. + /// Handle to the game font which may or may not be available for use yet. + public GameFontHandle GetGameFontHandle(GameFontStyle style) => this.gameFontManager.NewFontRef(style); + + /// + /// 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); + this.interfaceManager.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 + .GetAsync() + .ContinueWith(task => { - return this.InterfaceManagerWithSceneAsync - .ContinueWith(_ => Service.Get().RunOnFrameworkThread(func)) - .Unwrap(); - } - else + if (task.IsCompletedSuccessfully) + task.Result.AddNotification(content, title, type, msDelay); + }); + } + + /// + /// Unregister the UiBuilder. Do not call this in plugin code. + /// + void IDisposable.Dispose() + { + this.interfaceManager.Draw -= this.OnDraw; + this.interfaceManager.BuildFonts -= this.OnBuildFonts; + this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers; + } + + /// + /// Open the registered configuration UI, if it exists. + /// + internal void OpenConfig() + { + this.OpenConfigUi?.InvokeSafely(); + } + + /// + /// Notify this UiBuilder about plugin UI being hidden. + /// + internal void NotifyHideUi() + { + this.HideUi?.InvokeSafely(); + } + + /// + /// Notify this UiBuilder about plugin UI being shown. + /// + internal void NotifyShowUi() + { + this.ShowUi?.InvokeSafely(); + } + + private void OnDraw() + { + var configuration = Service.Get(); + var gameGui = Service.GetNullable(); + if (gameGui == null) + return; + + 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))) + { + if (!this.lastFrameUiHideState) { - return this.InterfaceManagerWithSceneAsync - .ContinueWith(_ => func()); + this.lastFrameUiHideState = true; + this.HideUi?.InvokeSafely(); } + + return; } - /// - /// Waits for UI to become available for use. - /// - /// Function to call. - /// Specifies whether to call the function from the framework thread. - /// A task that completes when the game's Present has been called at least once. - /// Return type. - public Task RunWhenUiPrepared(Func> func, bool runInFrameworkThread = false) - { - if (runInFrameworkThread) - { - return this.InterfaceManagerWithSceneAsync - .ContinueWith(_ => Service.Get().RunOnFrameworkThread(func)) - .Unwrap(); - } - else - { - return this.InterfaceManagerWithSceneAsync - .ContinueWith(_ => func()) - .Unwrap(); - } - } - - /// - /// Gets a game font. - /// - /// Font to get. - /// Handle to the game font which may or may not be available for use yet. - public GameFontHandle GetGameFontHandle(GameFontStyle style) => this.gameFontManager.NewFontRef(style); - - /// - /// 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); - this.interfaceManager.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 - .GetAsync() - .ContinueWith(task => - { - if (task.IsCompletedSuccessfully) - task.Result.AddNotification(content, title, type, msDelay); - }); - } - - /// - /// Unregister the UiBuilder. Do not call this in plugin code. - /// - void IDisposable.Dispose() - { - this.interfaceManager.Draw -= this.OnDraw; - this.interfaceManager.BuildFonts -= this.OnBuildFonts; - this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers; - } - - /// - /// Open the registered configuration UI, if it exists. - /// - internal void OpenConfig() - { - this.OpenConfigUi?.InvokeSafely(); - } - - /// - /// Notify this UiBuilder about plugin UI being hidden. - /// - internal void NotifyHideUi() - { - this.HideUi?.InvokeSafely(); - } - - /// - /// Notify this UiBuilder about plugin UI being shown. - /// - internal void NotifyShowUi() + if (this.lastFrameUiHideState) { + this.lastFrameUiHideState = false; this.ShowUi?.InvokeSafely(); } - private void OnDraw() + if (!this.interfaceManager.FontsReady) + return; + + ImGui.PushID(this.namespaceName); + if (DoStats) { - var configuration = Service.Get(); - var gameGui = Service.GetNullable(); - if (gameGui == null) - return; - - 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))) - { - if (!this.lastFrameUiHideState) - { - this.lastFrameUiHideState = true; - this.HideUi?.InvokeSafely(); - } - - return; - } - - if (this.lastFrameUiHideState) - { - this.lastFrameUiHideState = false; - this.ShowUi?.InvokeSafely(); - } - - if (!this.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?.InvokeSafely(); - } - 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(); + this.stopwatch.Restart(); } - private void OnBuildFonts() + if (this.hasErrorWindow && ImGui.Begin($"{this.namespaceName} Error", ref this.hasErrorWindow, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize)) { - this.BuildFonts?.InvokeSafely(); + 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(); } - private void OnAfterBuildFonts() + ImGuiManagedAsserts.ImGuiContextSnapshot snapshot = null; + if (this.Draw != null) { - this.AfterBuildFonts?.InvokeSafely(); + snapshot = ImGuiManagedAsserts.GetSnapshot(); } - private void OnResizeBuffers() + try { - this.ResizeBuffers?.InvokeSafely(); + this.Draw?.InvokeSafely(); } + 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?.InvokeSafely(); + } + + private void OnAfterBuildFonts() + { + this.AfterBuildFonts?.InvokeSafely(); + } + + private void OnResizeBuffers() + { + this.ResizeBuffers?.InvokeSafely(); } } diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index fc7777d42..5b186439d 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -4,338 +4,337 @@ 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. The size provided will be scaled by the global scale. + /// + 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. The size constraints provided will be scaled by the global scale. + /// + 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 ImGui window should display a close button in the title bar. + /// + public bool ShowCloseButton { get; set; } = true; + + /// + /// 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 always be executed before the open-state of the window is checked. + /// + public virtual void PreOpenCheck() + { + } + + /// + /// Additional conditions for the window to be drawn, regardless of its open-state. + /// + /// + /// True if the window should be drawn, false otherwise. + /// + /// + /// Not being drawn due to failing this condition will not change focus or trigger OnClose. + /// This is checked before PreDraw, but after Update. + /// + public virtual bool DrawConditions() + { + return 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() + { + } + + /// + /// Code to be executed every frame, even when the window is collapsed. + /// + public virtual void Update() + { + } + + /// + /// Draw the window via ImGui. + /// + internal void DrawInternal() + { + this.PreOpenCheck(); + + 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. The size provided will be scaled by the global scale. - /// - 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. The size constraints provided will be scaled by the global scale. - /// - 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 ImGui window should display a close button in the title bar. - /// - public bool ShowCloseButton { get; set; } = true; - - /// - /// 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 always be executed before the open-state of the window is checked. - /// - public virtual void PreOpenCheck() - { - } - - /// - /// Additional conditions for the window to be drawn, regardless of its open-state. - /// - /// - /// True if the window should be drawn, false otherwise. - /// - /// - /// Not being drawn due to failing this condition will not change focus or trigger OnClose. - /// This is checked before PreDraw, but after Update. - /// - public virtual bool DrawConditions() - { - return 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() - { - } - - /// - /// Code to be executed every frame, even when the window is collapsed. - /// - public virtual void Update() - { - } - - /// - /// Draw the window via ImGui. - /// - internal void DrawInternal() - { - this.PreOpenCheck(); - - if (!this.IsOpen) - { - if (this.internalIsOpen != this.internalLastIsOpen) - { - this.internalLastIsOpen = this.internalIsOpen; - this.OnClose(); - - this.IsFocused = false; - } - - return; - } - - this.Update(); - if (!this.DrawConditions()) - 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 (this.ShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags) : ImGui.Begin(this.WindowName, 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(); - // } - // } - // } + this.Update(); + if (!this.DrawConditions()) + return; - private void ApplyConditionals() + 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.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 (this.ShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags) : ImGui.Begin(this.WindowName, 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 b43194b22..3d53d974e 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -6,147 +6,146 @@ 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 read-only list of all s in this . + /// + public IReadOnlyList Windows => this.windows; + + /// + /// 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(); + + /// + /// Get a window by name. + /// + /// The name of the . + /// The object with matching name or null. + public Window? GetWindow(string windowName) => this.windows.FirstOrDefault(w => w.WindowName == windowName); + + /// + /// Draw all registered windows using ImGui. + /// + public void Draw() + { + var hasNamespace = !string.IsNullOrEmpty(this.Namespace); + + if (hasNamespace) + ImGui.PushID(this.Namespace); + + // Shallow clone the list of windows so that we can edit it without modifying it while the loop is iterating + foreach (var window in this.windows.ToArray()) { - 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 read-only list of all s in this . - /// - public IReadOnlyList Windows => this.windows; - - /// - /// 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(); - - /// - /// Get a window by name. - /// - /// The name of the . - /// The object with matching name or null. - public Window? GetWindow(string windowName) => this.windows.FirstOrDefault(w => w.WindowName == windowName); - - /// - /// Draw all registered windows using ImGui. - /// - public void Draw() - { - var hasNamespace = !string.IsNullOrEmpty(this.Namespace); - - if (hasNamespace) - ImGui.PushID(this.Namespace); - - // Shallow clone the list of windows so that we can edit it without modifying it while the loop is iterating - foreach (var window in this.windows.ToArray()) - { #if DEBUG // 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 e92af8c5b..857a5b682 100644 --- a/Dalamud/IoC/Internal/ObjectInstance.cs +++ b/Dalamud/IoC/Internal/ObjectInstance.cs @@ -2,33 +2,32 @@ using System; using System.Reflection; using System.Threading.Tasks; -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 + /// Weak reference to the underlying instance. + /// Type of the underlying instance. + public ObjectInstance(Task instanceTask, Type type) { - /// - /// Initializes a new instance of the class. - /// - /// Weak reference to the underlying instance. - /// Type of the underlying instance. - public ObjectInstance(Task instanceTask, Type type) - { - this.InstanceTask = instanceTask; - this.Version = type.GetCustomAttribute(); - } - - /// - /// Gets the current version of the instance, if it exists. - /// - public InterfaceVersionAttribute? Version { get; } - - /// - /// Gets a reference to the underlying instance. - /// - /// The underlying instance. - public Task InstanceTask { get; } + this.InstanceTask = instanceTask; + this.Version = type.GetCustomAttribute(); } + + /// + /// Gets the current version of the instance, if it exists. + /// + public InterfaceVersionAttribute? Version { get; } + + /// + /// Gets a reference to the underlying instance. + /// + /// The underlying instance. + public Task InstanceTask { get; } } diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index a04756851..041049643 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -7,236 +7,235 @@ using System.Threading.Tasks; 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, IServiceType { + 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. + /// Initializes a new instance of the class. /// - internal class ServiceContainer : IServiceProvider, IServiceType + public ServiceContainer() { - private static readonly ModuleLog Log = new("SERVICECONTAINER"); + } - private readonly Dictionary instances = new(); - - /// - /// Initializes a new instance of the class. - /// - public ServiceContainer() + /// + /// 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(Task instance) + { + if (instance == null) { + throw new ArgumentNullException(nameof(instance)); } - /// - /// 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(Task instance) + this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T)); + } + + /// + /// Create an object. + /// + /// The type of object to create. + /// Scoped objects to be included in the constructor. + /// The created object. + public async Task CreateAsync(Type objectType, params object[] scopedObjects) + { + var ctor = this.FindApplicableCtor(objectType, scopedObjects); + if (ctor == null) { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T)); - } - - /// - /// Create an object. - /// - /// The type of object to create. - /// Scoped objects to be included in the constructor. - /// The created object. - public async Task CreateAsync(Type objectType, params object[] scopedObjects) - { - 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); - }).ToList(); - - 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 = - await Task.WhenAll( - parameters - .Select(async p => - { - var service = await this.GetService(p.parameterType, scopedObjects); - - if (service == null) - { - Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName!); - } - - return service; - })); - - 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 (!await 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; - } - - /// - /// 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 async Task 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; - } - - foreach (var prop in props) - { - var service = await this.GetService(prop.propertyInfo.PropertyType, scopedObjects); - - if (service == null) - { - Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName!); - return false; - } - - prop.propertyInfo.SetValue(instance, service); - } - - return true; - } - - /// - 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 async Task GetService(Type serviceType, object[] scopedObjects) - { - var singletonService = await 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 async Task GetService(Type serviceType) - { - if (!this.instances.TryGetValue(serviceType, out var service)) - return null; - - var instance = await service.InstanceTask; - return instance.Target; - } - - 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; - } - } - + Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName!); return null; } - private bool ValidateCtor(ConstructorInfo ctor, Type[] types) + // validate dependency versions (if they exist) + var parameters = ctor.GetParameters().Select(p => { - var parameters = ctor.GetParameters(); - foreach (var parameter in parameters) + var parameterType = p.ParameterType; + var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; + return (parameterType, requiredVersion); + }).ToList(); + + 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 = + await Task.WhenAll( + parameters + .Select(async p => + { + var service = await this.GetService(p.parameterType, scopedObjects); + + if (service == null) + { + Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName!); + } + + return service; + })); + + 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 (!await 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; + } + + /// + /// 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 async Task 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 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; - } + 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; + } + + foreach (var prop in props) + { + var service = await this.GetService(prop.propertyInfo.PropertyType, scopedObjects); + + if (service == null) + { + Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName!); + return false; } - return true; + prop.propertyInfo.SetValue(instance, service); } + + return true; + } + + /// + 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 async Task GetService(Type serviceType, object[] scopedObjects) + { + var singletonService = await 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 async Task GetService(Type serviceType) + { + if (!this.instances.TryGetValue(serviceType, out var service)) + return null; + + var instance = await service.InstanceTask; + return instance.Target; + } + + 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 0cf107677..84d500cb3 100644 --- a/Dalamud/IoC/PluginServiceAttribute.cs +++ b/Dalamud/IoC/PluginServiceAttribute.cs @@ -2,14 +2,13 @@ using JetBrains.Annotations; -namespace Dalamud.IoC +namespace Dalamud.IoC; + +/// +/// This attribute indicates whether an applicable service should be injected into the plugin. +/// +[AttributeUsage(AttributeTargets.Property)] +[MeansImplicitUse(ImplicitUseKindFlags.Assign)] +public class PluginServiceAttribute : Attribute { - /// - /// This attribute indicates whether an applicable service should be injected into the plugin. - /// - [AttributeUsage(AttributeTargets.Property)] - [MeansImplicitUse(ImplicitUseKindFlags.Assign)] - 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 a050b71a0..51918a004 100644 --- a/Dalamud/Localization.cs +++ b/Dalamud/Localization.cs @@ -8,156 +8,155 @@ using CheapLoc; using Dalamud.Configuration.Internal; using Serilog; -namespace Dalamud +namespace Dalamud; + +/// +/// Class handling localization. +/// +[ServiceManager.EarlyLoadedService] +public class Localization : IServiceType { /// - /// Class handling localization. + /// Array of language codes which have a valid translation in Dalamud. /// - [ServiceManager.EarlyLoadedService] - public class Localization : IServiceType + public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru", "zh", "tw" }; + + 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", "zh", "tw" }; + this.locResourceDirectory = locResourceDirectory; + this.locResourcePrefix = locResourcePrefix; + this.useEmbedded = useEmbedded; + this.assembly = Assembly.GetCallingAssembly(); + } - private const string FallbackLangCode = "en"; + [ServiceManager.ServiceConstructor] + private Localization(Dalamud dalamud, DalamudConfiguration configuration) + : this(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_") + { + if (!string.IsNullOrEmpty(configuration.LanguageOverride)) + this.SetupWithLangCode(configuration.LanguageOverride); + else + this.SetupWithUiCulture(); + } - private readonly string locResourceDirectory; - private readonly string locResourcePrefix; - private readonly bool useEmbedded; - private readonly Assembly assembly; + /// + /// Delegate for the event that occurs when the language is changed. + /// + /// The language code of the new language. + public delegate void LocalizationChangedDelegate(string langCode); - /// - /// 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) + /// + /// 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 { - 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); - [ServiceManager.ServiceConstructor] - private Localization(Dalamud dalamud, DalamudConfiguration configuration) - : this(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_") - { - if (!string.IsNullOrEmpty(configuration.LanguageOverride)) - this.SetupWithLangCode(configuration.LanguageOverride); + if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) + { + this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName); + } else - this.SetupWithUiCulture(); - } - - /// - /// 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 - { - 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(); - } - } - 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) { 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 60afbef35..d93730f36 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -3,140 +3,139 @@ using System; using Serilog; using Serilog.Events; -namespace Dalamud.Logging.Internal +namespace Dalamud.Logging.Internal; + +/// +/// Class offering various methods to allow for logging in Dalamud modules. +/// +public class ModuleLog { + private readonly string moduleName; + private readonly ILogger moduleLogger; + /// - /// 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] ...". /// - public class ModuleLog + /// The module name. + public ModuleLog(string? moduleName) { - private readonly string moduleName; - private readonly ILogger moduleLogger; + // FIXME: Should be namespaced better, e.g. `Dalamud.PluginLoader`, but that becomes a relatively large + // change. + this.moduleName = moduleName ?? "DalamudInternal"; + this.moduleLogger = Log.ForContext("SourceContext", this.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) - { - // FIXME: Should be namespaced better, e.g. `Dalamud.PluginLoader`, but that becomes a relatively large - // change. - this.moduleName = moduleName ?? "DalamudInternal"; - this.moduleLogger = Log.ForContext("SourceContext", this.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) + => this.WriteLog(LogEventLevel.Verbose, messageTemplate, null, values); - /// - /// 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) - => this.WriteLog(LogEventLevel.Verbose, messageTemplate, null, 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) + => this.WriteLog(LogEventLevel.Verbose, messageTemplate, exception, 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) - => this.WriteLog(LogEventLevel.Verbose, messageTemplate, exception, 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) + => this.WriteLog(LogEventLevel.Debug, messageTemplate, null, 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) - => this.WriteLog(LogEventLevel.Debug, messageTemplate, null, 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) + => this.WriteLog(LogEventLevel.Debug, messageTemplate, exception, 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) - => this.WriteLog(LogEventLevel.Debug, messageTemplate, exception, 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) + => this.WriteLog(LogEventLevel.Information, messageTemplate, null, 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) - => this.WriteLog(LogEventLevel.Information, messageTemplate, null, 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) + => this.WriteLog(LogEventLevel.Information, messageTemplate, exception, 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) - => this.WriteLog(LogEventLevel.Information, messageTemplate, exception, 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) + => this.WriteLog(LogEventLevel.Warning, messageTemplate, null, 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) - => this.WriteLog(LogEventLevel.Warning, messageTemplate, null, 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) + => this.WriteLog(LogEventLevel.Warning, messageTemplate, exception, 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) - => this.WriteLog(LogEventLevel.Warning, messageTemplate, exception, 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) + => this.WriteLog(LogEventLevel.Error, messageTemplate, null, 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) - => this.WriteLog(LogEventLevel.Error, messageTemplate, null, 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) + => this.WriteLog(LogEventLevel.Error, messageTemplate, exception, 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) - => this.WriteLog(LogEventLevel.Error, messageTemplate, exception, 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) + => this.WriteLog(LogEventLevel.Fatal, messageTemplate, null, 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) - => this.WriteLog(LogEventLevel.Fatal, messageTemplate, null, 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) + => this.WriteLog(LogEventLevel.Fatal, messageTemplate, exception, 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) - => this.WriteLog(LogEventLevel.Fatal, messageTemplate, exception, values); - - private void WriteLog(LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values) - { - // FIXME: Eventually, the `pluginName` tag should be removed from here and moved over to the actual log - // formatter. - this.moduleLogger.Write( - level, - exception: exception, - messageTemplate: $"[{this.moduleName}] {messageTemplate}", - values); - } + private void WriteLog(LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values) + { + // FIXME: Eventually, the `pluginName` tag should be removed from here and moved over to the actual log + // formatter. + this.moduleLogger.Write( + level, + exception: exception, + messageTemplate: $"[{this.moduleName}] {messageTemplate}", + values); } } diff --git a/Dalamud/Logging/Internal/SerilogEventSink.cs b/Dalamud/Logging/Internal/SerilogEventSink.cs index eda1a0cb3..14baca456 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, LogEvent LogEvent)>? 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, LogEvent LogEvent)>? 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)); - } + this.LogLine?.Invoke(this, (message, logEvent)); } } diff --git a/Dalamud/Logging/Internal/TaskTracker.cs b/Dalamud/Logging/Internal/TaskTracker.cs index f4a02892e..a8729893f 100644 --- a/Dalamud/Logging/Internal/TaskTracker.cs +++ b/Dalamud/Logging/Internal/TaskTracker.cs @@ -7,238 +7,237 @@ using System.Threading.Tasks; using Dalamud.Game; -namespace Dalamud.Logging.Internal +namespace Dalamud.Logging.Internal; + +/// +/// Class responsible for tracking asynchronous tasks. +/// +[ServiceManager.EarlyLoadedService] +internal class TaskTracker : IDisposable, IServiceType { - /// - /// Class responsible for tracking asynchronous tasks. - /// - [ServiceManager.EarlyLoadedService] - internal class TaskTracker : IDisposable, IServiceType + private static readonly ModuleLog Log = new("TT"); + private static readonly List TrackedTasksInternal = new(); + private static readonly ConcurrentQueue NewlyCreatedTasks = new(); + private static bool clearRequested = false; + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + + private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; + private bool enabled = false; + + [ServiceManager.ServiceConstructor] + private 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; - - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); - - private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; - private bool enabled = false; - - [ServiceManager.ServiceConstructor] - private TaskTracker() - { #if DEBUG this.Enable(); #endif + } + + /// + /// 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) + { + 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++; + } - 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; } - - // 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; - } - } - } - - /// - /// Enables TaskTracker. - /// - public void Enable() - { - if (this.enabled) - return; - - this.ApplyPatch(); - - this.framework.Update += this.FrameworkOnUpdate; - this.enabled = true; - } - - /// - public void Dispose() - { - this.scheduleAndStartHook?.Dispose(); - - this.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; } } } + + /// + /// Enables TaskTracker. + /// + public void Enable() + { + if (this.enabled) + return; + + this.ApplyPatch(); + + this.framework.Update += this.FrameworkOnUpdate; + this.enabled = true; + } + + /// + public void Dispose() + { + this.scheduleAndStartHook?.Dispose(); + + this.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 bad839c25..a5aad330f 100644 --- a/Dalamud/Logging/PluginLog.cs +++ b/Dalamud/Logging/PluginLog.cs @@ -4,258 +4,257 @@ using System.Reflection; using Serilog; using Serilog.Events; -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 + /// The message template. + /// Values to log. + public static void Log(string messageTemplate, params object[] values) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, exception, values); + + #endregion + + #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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, exception, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, null, 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) + => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, exception, values); + + #endregion + + private static ILogger GetPluginLogger(string? pluginName) { - #region "Log" prefixed Serilog style methods + return Serilog.Log.ForContext("SourceContext", pluginName ?? string.Empty); + } - /// - /// 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, null, values); + private static void WriteLog(string? pluginName, LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values) + { + var pluginLogger = GetPluginLogger(pluginName); - /// - /// 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, exception, values); - - #endregion - - #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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Verbose, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Debug, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Information, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Warning, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Error, messageTemplate, exception, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, null, 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) - => WriteLog(Assembly.GetCallingAssembly().GetName().Name, LogEventLevel.Fatal, messageTemplate, exception, values); - - #endregion - - private static ILogger GetPluginLogger(string? pluginName) - { - return Serilog.Log.ForContext("SourceContext", pluginName ?? string.Empty); - } - - private static void WriteLog(string? pluginName, LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values) - { - var pluginLogger = GetPluginLogger(pluginName); - - // FIXME: Eventually, the `pluginName` tag should be removed from here and moved over to the actual log - // formatter. - pluginLogger.Write( - level, - exception: exception, - messageTemplate: $"[{pluginName}] {messageTemplate}", - values); - } + // FIXME: Eventually, the `pluginName` tag should be removed from here and moved over to the actual log + // formatter. + pluginLogger.Write( + level, + exception: exception, + messageTemplate: $"[{pluginName}] {messageTemplate}", + values); } } 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 5563f0599..f97461e66 100644 --- a/Dalamud/Memory/MemoryHelper.cs +++ b/Dalamud/Memory/MemoryHelper.cs @@ -12,746 +12,745 @@ 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, false); - - /// - /// 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 - - #region Game - - /// - /// Allocate memory in the game's UI memory space. - /// - /// Amount of bytes to allocate. - /// The alignment of the allocation. - /// Pointer to the allocated region. - public static IntPtr GameAllocateUi(ulong size, ulong alignment = 0) - { - return new IntPtr(IMemorySpace.GetUISpace()->Malloc(size, alignment)); - } - - /// - /// Allocate memory in the game's default memory space. - /// - /// Amount of bytes to allocate. - /// The alignment of the allocation. - /// Pointer to the allocated region. - public static IntPtr GameAllocateDefault(ulong size, ulong alignment = 0) - { - return new IntPtr(IMemorySpace.GetDefaultSpace()->Malloc(size, alignment)); - } - - /// - /// Allocate memory in the game's animation memory space. - /// - /// Amount of bytes to allocate. - /// The alignment of the allocation. - /// Pointer to the allocated region. - public static IntPtr GameAllocateAnimation(ulong size, ulong alignment = 0) - { - return new IntPtr(IMemorySpace.GetAnimationSpace()->Malloc(size, alignment)); - } - - /// - /// Allocate memory in the game's apricot memory space. - /// - /// Amount of bytes to allocate. - /// The alignment of the allocation. - /// Pointer to the allocated region. - public static IntPtr GameAllocateApricot(ulong size, ulong alignment = 0) - { - return new IntPtr(IMemorySpace.GetApricotSpace()->Malloc(size, alignment)); - } - - /// - /// Allocate memory in the game's sound memory space. - /// - /// Amount of bytes to allocate. - /// The alignment of the allocation. - /// Pointer to the allocated region. - public static IntPtr GameAllocateSound(ulong size, ulong alignment = 0) - { - return new IntPtr(IMemorySpace.GetSoundSpace()->Malloc(size, alignment)); - } - - /// - /// Free memory in the game's memory space. - /// - /// The memory you are freeing must be allocated with game allocators. - /// Position at which the memory to be freed is located. - /// Amount of bytes to free. - public static void GameFree(ref IntPtr ptr, ulong size) - { - if (ptr == IntPtr.Zero) - { - return; - } - - IMemorySpace.Free((void*)ptr, size); - ptr = IntPtr.Zero; - } - - #endregion - - #region Utility - - /// - /// Null-terminate a byte array. - /// - /// The byte array to terminate. - /// The terminated byte array. - public static byte[] NullTerminate(this byte[] bytes) - { - if (bytes.Length == 0 || bytes[^1] != 0) - { - var newBytes = new byte[bytes.Length + 1]; - Array.Copy(bytes, newBytes, bytes.Length); - newBytes[^1] = 0; - - return newBytes; - } - - return bytes; - } - - #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, false); + + /// + /// 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 + + #region Game + + /// + /// Allocate memory in the game's UI memory space. + /// + /// Amount of bytes to allocate. + /// The alignment of the allocation. + /// Pointer to the allocated region. + public static IntPtr GameAllocateUi(ulong size, ulong alignment = 0) + { + return new IntPtr(IMemorySpace.GetUISpace()->Malloc(size, alignment)); + } + + /// + /// Allocate memory in the game's default memory space. + /// + /// Amount of bytes to allocate. + /// The alignment of the allocation. + /// Pointer to the allocated region. + public static IntPtr GameAllocateDefault(ulong size, ulong alignment = 0) + { + return new IntPtr(IMemorySpace.GetDefaultSpace()->Malloc(size, alignment)); + } + + /// + /// Allocate memory in the game's animation memory space. + /// + /// Amount of bytes to allocate. + /// The alignment of the allocation. + /// Pointer to the allocated region. + public static IntPtr GameAllocateAnimation(ulong size, ulong alignment = 0) + { + return new IntPtr(IMemorySpace.GetAnimationSpace()->Malloc(size, alignment)); + } + + /// + /// Allocate memory in the game's apricot memory space. + /// + /// Amount of bytes to allocate. + /// The alignment of the allocation. + /// Pointer to the allocated region. + public static IntPtr GameAllocateApricot(ulong size, ulong alignment = 0) + { + return new IntPtr(IMemorySpace.GetApricotSpace()->Malloc(size, alignment)); + } + + /// + /// Allocate memory in the game's sound memory space. + /// + /// Amount of bytes to allocate. + /// The alignment of the allocation. + /// Pointer to the allocated region. + public static IntPtr GameAllocateSound(ulong size, ulong alignment = 0) + { + return new IntPtr(IMemorySpace.GetSoundSpace()->Malloc(size, alignment)); + } + + /// + /// Free memory in the game's memory space. + /// + /// The memory you are freeing must be allocated with game allocators. + /// Position at which the memory to be freed is located. + /// Amount of bytes to free. + public static void GameFree(ref IntPtr ptr, ulong size) + { + if (ptr == IntPtr.Zero) + { + return; + } + + IMemorySpace.Free((void*)ptr, size); + ptr = IntPtr.Zero; + } + + #endregion + + #region Utility + + /// + /// Null-terminate a byte array. + /// + /// The byte array to terminate. + /// The terminated byte array. + public static byte[] NullTerminate(this byte[] bytes) + { + if (bytes.Length == 0 || bytes[^1] != 0) + { + var newBytes = new byte[bytes.Length + 1]; + Array.Copy(bytes, newBytes, bytes.Length); + newBytes[^1] = 0; + + return newBytes; + } + + return bytes; + } + + #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 ce46a69a6..b05aecc7a 100644 --- a/Dalamud/NativeFunctions.cs +++ b/Dalamud/NativeFunctions.cs @@ -4,1956 +4,1955 @@ 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/synchapi/nf-synchapi-setevent - /// Sets the specified event object to the signaled state. + /// 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 event object. The CreateEvent or OpenEvent 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 GetLastError. - /// - [DllImport("kernel32.dll")] - public static extern bool SetEvent(IntPtr hEvent); + Help = 0x4000, /// - /// 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. + /// The message box contains one push button: OK. This is the default. /// - /// - /// 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); + Ok = DefaultValue, /// - /// 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 two push buttons: OK and Cancel. /// - /// - /// 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); + OkCancel = 0x1, /// - /// 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: Retry 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); + RetryCancel = 0x5, /// - /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// The message box contains two push buttons: Yes and No. /// - /// - /// 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); + YesNo = 0x4, /// - /// 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 three push buttons: Yes, No, and Cancel. /// - /// - /// 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); + 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, - IntPtr lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesRead); + IconExclamation = 0x30, /// - /// 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); + IconWarning = IconExclamation, /// - /// 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 icon consisting of a lowercase letter i in a circle 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); + IconInformation = 0x40, /// - /// 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); + IconAsterisk = IconInformation, /// - /// 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. + /// 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. /// - /// - /// 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); + IconQuestion = 0x20, /// - /// 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 stop-sign icon appears in the message box. /// - /// - /// 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); + IconStop = 0x10, /// - /// 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); + IconError = IconStop, /// - /// 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); + IconHand = IconStop, + + // To indicate the default button, specify one of the following values. /// - /// Get a handle to the current process. + /// The first button is the default button. + /// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified. /// - /// Handle to the process. - [DllImport("kernel32.dll")] - public static extern IntPtr GetCurrentProcess(); + DefButton1 = DefaultValue, /// - /// Get the current process ID. + /// The second button is the default button. /// - /// The process ID. - [DllImport("kernel32.dll")] - public static extern uint GetCurrentProcessId(); + DefButton2 = 0x100, /// - /// Get the current thread ID. + /// The third button is the default button. /// - /// The thread ID. - [DllImport("kernel32.dll")] - public static extern uint GetCurrentThreadId(); + 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. /// - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Native funcs")] - 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; - - /// - /// Pointer to the exception record. - /// - public IntPtr ExceptionPointers; - - /// - /// ClientPointers field. - /// - public int ClientPointers; - } + UserData = -21, /// - /// Finds window according to conditions. + /// Sets a new address for the window procedure. /// - /// Handle to parent window. - /// Window to search after. - /// Name of class. - /// Name of window. - /// Found window, or null. - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern IntPtr FindWindowEx( - IntPtr parentHandle, - IntPtr childAfter, - string className, - IntPtr windowTitle); + 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), } /// - /// 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/synchapi/nf-synchapi-setevent + /// Sets the specified event object to the signaled state. + /// + /// A handle to the event object. The CreateEvent or OpenEvent 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 GetLastError. + /// + [DllImport("kernel32.dll")] + public static extern bool SetEvent(IntPtr hEvent); + + /// + /// 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. +/// +[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Native funcs")] +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; + } + + /// + /// Finds window according to conditions. + /// + /// Handle to parent window. + /// Window to search after. + /// Name of class. + /// Name of window. + /// Found window, or null. + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr FindWindowEx( + IntPtr parentHandle, + IntPtr childAfter, + string className, + IntPtr windowTitle); +} + +/// +/// 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 055b34cb1..2a1e3227d 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -23,412 +23,411 @@ using Dalamud.Plugin.Ipc.Exceptions; using Dalamud.Plugin.Ipc.Internal; using Dalamud.Utility; -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 + this.UiLanguage = "en"; + } + + localization.LocalizationChanged += this.OnLocalizationChanged; + configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; + } + + /// + /// Delegate for localization change with two-letter iso lang code. + /// + /// The new language code. + public delegate void LanguageChangedDelegate(string langCode); + + /// + /// Event that gets fired when loc is changed + /// + public event LanguageChangedDelegate LanguageChanged; + + /// + /// Gets the reason this plugin was loaded. + /// + public PluginLoadReason Reason { get; } + + /// + /// Gets a value indicating whether this is a dev plugin. + /// + public bool IsDev { get; } + + /// + /// Gets the time that this plugin was loaded. + /// + public DateTime LoadTime { get; } + + /// + /// Gets the UTC time that this plugin was loaded. + /// + public DateTime LoadTimeUTC { get; } + + /// + /// Gets the timespan delta from when this plugin was loaded. + /// + public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; + + /// + /// Gets the directory Dalamud assets are stored in. + /// + public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; + + /// + /// Gets the location of your plugin assembly. + /// + public FileInfo AssemblyLocation { get; } + + /// + /// Gets the directory your plugin configurations are stored in. + /// + public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); + + /// + /// Gets the config file of your plugin. + /// + public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName); + + /// + /// 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. + /// + public bool IsDevMenuOpen => Service.GetNullable() is { IsDevMenuOpen: true }; // Can be null during boot + + /// + /// Gets a value indicating whether a debugger is attached. + /// + public bool IsDebugging => Debugger.IsAttached; + + /// + /// 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 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 internal names. + /// + public List PluginInternalNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.InternalName).ToList(); + + #region IPC + + /// + public T GetOrCreateData(string tag, Func dataGenerator) where T : class + => Service.Get().GetOrCreateData(tag, dataGenerator); + + /// + public void RelinquishData(string tag) + => Service.Get().RelinquishData(tag); + + /// + public bool TryGetData(string tag, [NotNullWhen(true)] out T? data) where T : class + => Service.Get().TryGetData(tag, out data); + + /// + public T? GetData(string tag) where T : class + => Service.Get().GetData(tag); + + /// + /// 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); + + /// + /// 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); + + #endregion + + #region Configuration + + /// + /// 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 (type.IsAssignableTo(typeof(IPluginConfiguration))) { - var currentUiLang = CultureInfo.CurrentUICulture; - if (Localization.ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) - this.UiLanguage = currentUiLang.TwoLetterISOLanguageName; - else - this.UiLanguage = "en"; + var mi = this.configs.GetType().GetMethod("LoadForType"); + var fn = mi.MakeGenericMethod(type); + return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName }); } - - localization.LocalizationChanged += this.OnLocalizationChanged; - configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; } - /// - /// Delegate for localization change with two-letter iso lang code. - /// - /// The new language code. - public delegate void LanguageChangedDelegate(string langCode); - - /// - /// Event that gets fired when loc is changed - /// - public event LanguageChangedDelegate LanguageChanged; - - /// - /// Gets the reason this plugin was loaded. - /// - public PluginLoadReason Reason { get; } - - /// - /// Gets a value indicating whether this is a dev plugin. - /// - public bool IsDev { get; } - - /// - /// Gets the time that this plugin was loaded. - /// - public DateTime LoadTime { get; } - - /// - /// Gets the UTC time that this plugin was loaded. - /// - public DateTime LoadTimeUTC { get; } - - /// - /// Gets the timespan delta from when this plugin was loaded. - /// - public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; - - /// - /// Gets the directory Dalamud assets are stored in. - /// - public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; - - /// - /// Gets the location of your plugin assembly. - /// - public FileInfo AssemblyLocation { get; } - - /// - /// Gets the directory your plugin configurations are stored in. - /// - public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); - - /// - /// Gets the config file of your plugin. - /// - public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName); - - /// - /// 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. - /// - public bool IsDevMenuOpen => Service.GetNullable() is { IsDevMenuOpen: true }; // Can be null during boot - - /// - /// Gets a value indicating whether a debugger is attached. - /// - public bool IsDebugging => Debugger.IsAttached; - - /// - /// 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 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 internal names. - /// - public List PluginInternalNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.InternalName).ToList(); - - #region IPC - - /// - public T GetOrCreateData(string tag, Func dataGenerator) where T : class - => Service.Get().GetOrCreateData(tag, dataGenerator); - - /// - public void RelinquishData(string tag) - => Service.Get().RelinquishData(tag); - - /// - public bool TryGetData(string tag, [NotNullWhen(true)] out T? data) where T : class - => Service.Get().TryGetData(tag, out data); - - /// - public T? GetData(string tag) where T : class - => Service.Get().GetData(tag); - - /// - /// 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); - - /// - /// 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); - - #endregion - - #region Configuration - - /// - /// 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 (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 }); - } - } - - // 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); - - /// - /// Get the loc directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. - public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc")); - - #endregion - - #region Chat Links - - /// - /// 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); - } - - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - public void RemoveChatLinkHandler(uint commandId) - { - Service.Get().RemoveChatLinkHandler(this.pluginName, commandId); - } - - /// - /// Removes all chat link handlers registered by the plugin. - /// - public void RemoveChatLinkHandler() - { - Service.Get().RemoveChatLinkHandler(this.pluginName); - } - #endregion - - #region Dependency Injection - - /// - /// 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(); - - var realScopedObjects = new object[scopedObjects.Length + 1]; - realScopedObjects[0] = this; - Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - - return (T)svcContainer.CreateAsync(typeof(T), realScopedObjects).GetAwaiter().GetResult(); - } - - /// - /// 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(); - - var realScopedObjects = new object[scopedObjects.Length + 1]; - realScopedObjects[0] = this; - Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - - return svcContainer.InjectProperties(instance, realScopedObjects).GetAwaiter().GetResult(); - } - - #endregion - - /// - /// Unregister your plugin and dispose all references. - /// - void IDisposable.Dispose() - { - this.UiBuilder.ExplicitDispose(); - Service.Get().RemoveChatLinkHandler(this.pluginName); - Service.Get().LocalizationChanged -= this.OnLocalizationChanged; - Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; - } - - /// - /// Obsolete implicit dispose implementation. Should not be used. - /// - [Obsolete("Do not dispose \"DalamudPluginInterface\".", true)] - public void Dispose() - { - // ignored - } - - private void OnLocalizationChanged(string langCode) - { - this.UiLanguage = langCode; - this.LanguageChanged?.Invoke(langCode); - } - - private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) - { - this.GeneralChatType = dalamudConfiguration.GeneralChatType; - } + // 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); + + /// + /// Get the loc directory. + /// + /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. + public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc")); + + #endregion + + #region Chat Links + + /// + /// 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); + } + + /// + /// Remove a chat link handler. + /// + /// The ID of the command. + public void RemoveChatLinkHandler(uint commandId) + { + Service.Get().RemoveChatLinkHandler(this.pluginName, commandId); + } + + /// + /// Removes all chat link handlers registered by the plugin. + /// + public void RemoveChatLinkHandler() + { + Service.Get().RemoveChatLinkHandler(this.pluginName); + } + #endregion + + #region Dependency Injection + + /// + /// 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(); + + var realScopedObjects = new object[scopedObjects.Length + 1]; + realScopedObjects[0] = this; + Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); + + return (T)svcContainer.CreateAsync(typeof(T), realScopedObjects).GetAwaiter().GetResult(); + } + + /// + /// 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(); + + var realScopedObjects = new object[scopedObjects.Length + 1]; + realScopedObjects[0] = this; + Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); + + return svcContainer.InjectProperties(instance, realScopedObjects).GetAwaiter().GetResult(); + } + + #endregion + + /// + /// Unregister your plugin and dispose all references. + /// + void IDisposable.Dispose() + { + this.UiBuilder.ExplicitDispose(); + Service.Get().RemoveChatLinkHandler(this.pluginName); + Service.Get().LocalizationChanged -= this.OnLocalizationChanged; + Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; + } + + /// + /// Obsolete implicit dispose implementation. Should not be used. + /// + [Obsolete("Do not dispose \"DalamudPluginInterface\".", true)] + public void Dispose() + { + // ignored + } + + 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 0488f5539..d158cb8b7 100644 --- a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs @@ -1,24 +1,23 @@ 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 a2d8e7361..90905cd6d 100644 --- a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs @@ -1,22 +1,21 @@ -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/Loader/AssemblyLoadContextBuilder.cs b/Dalamud/Plugin/Internal/Loader/AssemblyLoadContextBuilder.cs index a4b75f7d3..b7a2ffe2e 100644 --- a/Dalamud/Plugin/Internal/Loader/AssemblyLoadContextBuilder.cs +++ b/Dalamud/Plugin/Internal/Loader/AssemblyLoadContextBuilder.cs @@ -9,308 +9,307 @@ using System.Runtime.Loader; using Dalamud.Plugin.Internal.Loader.LibraryModel; -namespace Dalamud.Plugin.Internal.Loader +namespace Dalamud.Plugin.Internal.Loader; + +/// +/// A builder for creating an instance of . +/// +internal class AssemblyLoadContextBuilder { + private readonly List additionalProbingPaths = new(); + private readonly List resourceProbingPaths = new(); + private readonly List resourceProbingSubpaths = new(); + private readonly Dictionary managedLibraries = new(StringComparer.Ordinal); + private readonly Dictionary nativeLibraries = new(StringComparer.Ordinal); + private readonly HashSet privateAssemblies = new(StringComparer.Ordinal); + private readonly HashSet defaultAssemblies = new(StringComparer.Ordinal); + private AssemblyLoadContext defaultLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; + private string? mainAssemblyPath; + private bool preferDefaultLoadContext; + + private bool isCollectible; + private bool loadInMemory; + private bool shadowCopyNativeLibraries; + /// - /// A builder for creating an instance of . + /// Creates an assembly load context using settings specified on the builder. /// - internal class AssemblyLoadContextBuilder + /// A new ManagedLoadContext. + public AssemblyLoadContext Build() { - private readonly List additionalProbingPaths = new(); - private readonly List resourceProbingPaths = new(); - private readonly List resourceProbingSubpaths = new(); - private readonly Dictionary managedLibraries = new(StringComparer.Ordinal); - private readonly Dictionary nativeLibraries = new(StringComparer.Ordinal); - private readonly HashSet privateAssemblies = new(StringComparer.Ordinal); - private readonly HashSet defaultAssemblies = new(StringComparer.Ordinal); - private AssemblyLoadContext defaultLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; - private string? mainAssemblyPath; - private bool preferDefaultLoadContext; - - private bool isCollectible; - private bool loadInMemory; - private bool shadowCopyNativeLibraries; - - /// - /// Creates an assembly load context using settings specified on the builder. - /// - /// A new ManagedLoadContext. - public AssemblyLoadContext Build() + var resourceProbingPaths = new List(this.resourceProbingPaths); + foreach (var additionalPath in this.additionalProbingPaths) { - var resourceProbingPaths = new List(this.resourceProbingPaths); - foreach (var additionalPath in this.additionalProbingPaths) + foreach (var subPath in this.resourceProbingSubpaths) { - foreach (var subPath in this.resourceProbingSubpaths) - { - resourceProbingPaths.Add(Path.Combine(additionalPath, subPath)); - } + resourceProbingPaths.Add(Path.Combine(additionalPath, subPath)); + } + } + + if (this.mainAssemblyPath == null) + throw new InvalidOperationException($"Missing required property. You must call '{nameof(this.SetMainAssemblyPath)}' to configure the default assembly."); + + return new ManagedLoadContext( + this.mainAssemblyPath, + this.managedLibraries, + this.nativeLibraries, + this.privateAssemblies, + this.defaultAssemblies, + this.additionalProbingPaths, + resourceProbingPaths, + this.defaultLoadContext, + this.preferDefaultLoadContext, + this.isCollectible, + this.loadInMemory, + this.shadowCopyNativeLibraries); + } + + /// + /// Set the file path to the main assembly for the context. This is used as the starting point for loading + /// other assemblies. The directory that contains it is also known as the 'app local' directory. + /// + /// The file path. Must not be null or empty. Must be an absolute path. + /// The builder. + public AssemblyLoadContextBuilder SetMainAssemblyPath(string path) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Argument must not be null or empty.", nameof(path)); + + if (!Path.IsPathRooted(path)) + throw new ArgumentException("Argument must be a full path.", nameof(path)); + + this.mainAssemblyPath = path; + + return this; + } + + /// + /// Replaces the default used by the . + /// Use this feature if the of the is not the Runtime's default load context. + /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != . + /// + /// The context to set. + /// The builder. + public AssemblyLoadContextBuilder SetDefaultContext(AssemblyLoadContext context) + { + this.defaultLoadContext = context ?? throw new ArgumentException($"Bad Argument: AssemblyLoadContext in {nameof(AssemblyLoadContextBuilder)}.{nameof(this.SetDefaultContext)} is null."); + + return this; + } + + /// + /// Instructs the load context to prefer a private version of this assembly, even if that version is + /// different from the version used by the host application. + /// Use this when you do not need to exchange types created from within the load context with other contexts + /// or the default app context. + /// + /// This may mean the types loaded from + /// this assembly will not match the types from an assembly with the same name, but different version, + /// in the host application. + /// + /// + /// For example, if the host application has a type named Foo from assembly Banana, Version=1.0.0.0 + /// and the load context prefers a private version of Banan, Version=2.0.0.0, when comparing two objects, + /// one created by the host (Foo1) and one created from within the load context (Foo2), they will not have the same + /// type. Foo1.GetType() != Foo2.GetType(). + /// + /// + /// The name of the assembly. + /// The builder. + public AssemblyLoadContextBuilder PreferLoadContextAssembly(AssemblyName assemblyName) + { + if (assemblyName.Name != null) + this.privateAssemblies.Add(assemblyName.Name); + + return this; + } + + /// + /// Instructs the load context to first attempt to load assemblies by this name from the default app context, even + /// if other assemblies in this load context express a dependency on a higher or lower version. + /// Use this when you need to exchange types created from within the load context with other contexts + /// or the default app context. + /// + /// The name of the assembly. + /// The builder. + public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName) + { + var names = new Queue(new[] { assemblyName }); + + while (names.TryDequeue(out var name)) + { + if (name.Name == null || this.defaultAssemblies.Contains(name.Name)) + { + // base cases + continue; } - if (this.mainAssemblyPath == null) - throw new InvalidOperationException($"Missing required property. You must call '{nameof(this.SetMainAssemblyPath)}' to configure the default assembly."); + this.defaultAssemblies.Add(name.Name); - return new ManagedLoadContext( - this.mainAssemblyPath, - this.managedLibraries, - this.nativeLibraries, - this.privateAssemblies, - this.defaultAssemblies, - this.additionalProbingPaths, - resourceProbingPaths, - this.defaultLoadContext, - this.preferDefaultLoadContext, - this.isCollectible, - this.loadInMemory, - this.shadowCopyNativeLibraries); - } + // Load and find all dependencies of default assemblies. + // This sacrifices some performance for determinism in how transitive + // dependencies will be shared between host and plugin. + var assembly = this.defaultLoadContext.LoadFromAssemblyName(name); - /// - /// Set the file path to the main assembly for the context. This is used as the starting point for loading - /// other assemblies. The directory that contains it is also known as the 'app local' directory. - /// - /// The file path. Must not be null or empty. Must be an absolute path. - /// The builder. - public AssemblyLoadContextBuilder SetMainAssemblyPath(string path) - { - if (string.IsNullOrEmpty(path)) - throw new ArgumentException("Argument must not be null or empty.", nameof(path)); - - if (!Path.IsPathRooted(path)) - throw new ArgumentException("Argument must be a full path.", nameof(path)); - - this.mainAssemblyPath = path; - - return this; - } - - /// - /// Replaces the default used by the . - /// Use this feature if the of the is not the Runtime's default load context. - /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != . - /// - /// The context to set. - /// The builder. - public AssemblyLoadContextBuilder SetDefaultContext(AssemblyLoadContext context) - { - this.defaultLoadContext = context ?? throw new ArgumentException($"Bad Argument: AssemblyLoadContext in {nameof(AssemblyLoadContextBuilder)}.{nameof(this.SetDefaultContext)} is null."); - - return this; - } - - /// - /// Instructs the load context to prefer a private version of this assembly, even if that version is - /// different from the version used by the host application. - /// Use this when you do not need to exchange types created from within the load context with other contexts - /// or the default app context. - /// - /// This may mean the types loaded from - /// this assembly will not match the types from an assembly with the same name, but different version, - /// in the host application. - /// - /// - /// For example, if the host application has a type named Foo from assembly Banana, Version=1.0.0.0 - /// and the load context prefers a private version of Banan, Version=2.0.0.0, when comparing two objects, - /// one created by the host (Foo1) and one created from within the load context (Foo2), they will not have the same - /// type. Foo1.GetType() != Foo2.GetType(). - /// - /// - /// The name of the assembly. - /// The builder. - public AssemblyLoadContextBuilder PreferLoadContextAssembly(AssemblyName assemblyName) - { - if (assemblyName.Name != null) - this.privateAssemblies.Add(assemblyName.Name); - - return this; - } - - /// - /// Instructs the load context to first attempt to load assemblies by this name from the default app context, even - /// if other assemblies in this load context express a dependency on a higher or lower version. - /// Use this when you need to exchange types created from within the load context with other contexts - /// or the default app context. - /// - /// The name of the assembly. - /// The builder. - public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName) - { - var names = new Queue(new[] { assemblyName }); - - while (names.TryDequeue(out var name)) + foreach (var reference in assembly.GetReferencedAssemblies()) { - if (name.Name == null || this.defaultAssemblies.Contains(name.Name)) - { - // base cases - continue; - } - - this.defaultAssemblies.Add(name.Name); - - // Load and find all dependencies of default assemblies. - // This sacrifices some performance for determinism in how transitive - // dependencies will be shared between host and plugin. - var assembly = this.defaultLoadContext.LoadFromAssemblyName(name); - - foreach (var reference in assembly.GetReferencedAssemblies()) - { - names.Enqueue(reference); - } + names.Enqueue(reference); } - - return this; } - /// - /// Instructs the load context to first search for binaries from the default app context, even - /// if other assemblies in this load context express a dependency on a higher or lower version. - /// Use this when you need to exchange types created from within the load context with other contexts - /// or the default app context. - /// - /// This may mean the types loaded from within the context are force-downgraded to the version provided - /// by the host. can be used to selectively identify binaries - /// which should not be loaded from the default load context. - /// - /// - /// When true, first attemp to load binaries from the default load context. - /// The builder. - public AssemblyLoadContextBuilder PreferDefaultLoadContext(bool preferDefaultLoadContext) + return this; + } + + /// + /// Instructs the load context to first search for binaries from the default app context, even + /// if other assemblies in this load context express a dependency on a higher or lower version. + /// Use this when you need to exchange types created from within the load context with other contexts + /// or the default app context. + /// + /// This may mean the types loaded from within the context are force-downgraded to the version provided + /// by the host. can be used to selectively identify binaries + /// which should not be loaded from the default load context. + /// + /// + /// When true, first attemp to load binaries from the default load context. + /// The builder. + public AssemblyLoadContextBuilder PreferDefaultLoadContext(bool preferDefaultLoadContext) + { + this.preferDefaultLoadContext = preferDefaultLoadContext; + + return this; + } + + /// + /// Add a managed library to the load context. + /// + /// The managed library. + /// The builder. + public AssemblyLoadContextBuilder AddManagedLibrary(ManagedLibrary library) + { + ValidateRelativePath(library.AdditionalProbingPath); + + if (library.Name.Name != null) { - this.preferDefaultLoadContext = preferDefaultLoadContext; - - return this; + this.managedLibraries.Add(library.Name.Name, library); } - /// - /// Add a managed library to the load context. - /// - /// The managed library. - /// The builder. - public AssemblyLoadContextBuilder AddManagedLibrary(ManagedLibrary library) - { - ValidateRelativePath(library.AdditionalProbingPath); + return this; + } - if (library.Name.Name != null) - { - this.managedLibraries.Add(library.Name.Name, library); - } + /// + /// Add a native library to the load context. + /// + /// A native library. + /// The builder. + public AssemblyLoadContextBuilder AddNativeLibrary(NativeLibrary library) + { + ValidateRelativePath(library.AppLocalPath); + ValidateRelativePath(library.AdditionalProbingPath); - return this; - } + this.nativeLibraries.Add(library.Name, library); - /// - /// Add a native library to the load context. - /// - /// A native library. - /// The builder. - public AssemblyLoadContextBuilder AddNativeLibrary(NativeLibrary library) - { - ValidateRelativePath(library.AppLocalPath); - ValidateRelativePath(library.AdditionalProbingPath); + return this; + } - this.nativeLibraries.Add(library.Name, library); + /// + /// Add a that should be used to search for native and managed libraries. + /// + /// The file path. Must be a full file path. + /// The builder. + public AssemblyLoadContextBuilder AddProbingPath(string path) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value must not be null or empty.", nameof(path)); - return this; - } + if (!Path.IsPathRooted(path)) + throw new ArgumentException("Argument must be a full path.", nameof(path)); - /// - /// Add a that should be used to search for native and managed libraries. - /// - /// The file path. Must be a full file path. - /// The builder. - public AssemblyLoadContextBuilder AddProbingPath(string path) - { - if (string.IsNullOrEmpty(path)) - throw new ArgumentException("Value must not be null or empty.", nameof(path)); + this.additionalProbingPaths.Add(path); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("Argument must be a full path.", nameof(path)); + return this; + } - this.additionalProbingPaths.Add(path); + /// + /// Add a that should be use to search for resource assemblies (aka satellite assemblies). + /// + /// The file path. Must be a full file path. + /// The builder. + public AssemblyLoadContextBuilder AddResourceProbingPath(string path) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value must not be null or empty.", nameof(path)); - return this; - } + if (!Path.IsPathRooted(path)) + throw new ArgumentException("Argument must be a full path.", nameof(path)); - /// - /// Add a that should be use to search for resource assemblies (aka satellite assemblies). - /// - /// The file path. Must be a full file path. - /// The builder. - public AssemblyLoadContextBuilder AddResourceProbingPath(string path) - { - if (string.IsNullOrEmpty(path)) - throw new ArgumentException("Value must not be null or empty.", nameof(path)); + this.resourceProbingPaths.Add(path); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("Argument must be a full path.", nameof(path)); + return this; + } - this.resourceProbingPaths.Add(path); + /// + /// Enable unloading the assembly load context. + /// + /// The builder. + public AssemblyLoadContextBuilder EnableUnloading() + { + this.isCollectible = true; - return this; - } + return this; + } - /// - /// Enable unloading the assembly load context. - /// - /// The builder. - public AssemblyLoadContextBuilder EnableUnloading() - { - this.isCollectible = true; + /// + /// Read .dll files into memory to avoid locking the files. + /// This is not as efficient, so is not enabled by default, but is required for scenarios + /// like hot reloading. + /// + /// The builder. + public AssemblyLoadContextBuilder PreloadAssembliesIntoMemory() + { + this.loadInMemory = true; - return this; - } + return this; + } - /// - /// Read .dll files into memory to avoid locking the files. - /// This is not as efficient, so is not enabled by default, but is required for scenarios - /// like hot reloading. - /// - /// The builder. - public AssemblyLoadContextBuilder PreloadAssembliesIntoMemory() - { - this.loadInMemory = true; + /// + /// Shadow copy native libraries (unmanaged DLLs) to avoid locking of these files. + /// This is not as efficient, so is not enabled by default, but is required for scenarios + /// like hot reloading of plugins dependent on native libraries. + /// + /// The builder. + public AssemblyLoadContextBuilder ShadowCopyNativeLibraries() + { + this.shadowCopyNativeLibraries = true; - return this; - } + return this; + } - /// - /// Shadow copy native libraries (unmanaged DLLs) to avoid locking of these files. - /// This is not as efficient, so is not enabled by default, but is required for scenarios - /// like hot reloading of plugins dependent on native libraries. - /// - /// The builder. - public AssemblyLoadContextBuilder ShadowCopyNativeLibraries() - { - this.shadowCopyNativeLibraries = true; + /// + /// Add a that should be use to search for resource assemblies (aka satellite assemblies) + /// relative to any paths specified as . + /// + /// The file path. Must not be a full file path since it will be appended to additional probing path roots. + /// The builder. + internal AssemblyLoadContextBuilder AddResourceProbingSubpath(string path) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value must not be null or empty.", nameof(path)); - return this; - } + if (Path.IsPathRooted(path)) + throw new ArgumentException("Argument must be not a full path.", nameof(path)); - /// - /// Add a that should be use to search for resource assemblies (aka satellite assemblies) - /// relative to any paths specified as . - /// - /// The file path. Must not be a full file path since it will be appended to additional probing path roots. - /// The builder. - internal AssemblyLoadContextBuilder AddResourceProbingSubpath(string path) - { - if (string.IsNullOrEmpty(path)) - throw new ArgumentException("Value must not be null or empty.", nameof(path)); + this.resourceProbingSubpaths.Add(path); - if (Path.IsPathRooted(path)) - throw new ArgumentException("Argument must be not a full path.", nameof(path)); + return this; + } - this.resourceProbingSubpaths.Add(path); + private static void ValidateRelativePath(string probingPath) + { + if (string.IsNullOrEmpty(probingPath)) + throw new ArgumentException("Value must not be null or empty.", nameof(probingPath)); - return this; - } - - private static void ValidateRelativePath(string probingPath) - { - if (string.IsNullOrEmpty(probingPath)) - throw new ArgumentException("Value must not be null or empty.", nameof(probingPath)); - - if (Path.IsPathRooted(probingPath)) - throw new ArgumentException("Argument must be a relative path.", nameof(probingPath)); - } + if (Path.IsPathRooted(probingPath)) + throw new ArgumentException("Argument must be a relative path.", nameof(probingPath)); } } diff --git a/Dalamud/Plugin/Internal/Loader/LibraryModel/ManagedLibrary.cs b/Dalamud/Plugin/Internal/Loader/LibraryModel/ManagedLibrary.cs index 353c07b96..386184c28 100644 --- a/Dalamud/Plugin/Internal/Loader/LibraryModel/ManagedLibrary.cs +++ b/Dalamud/Plugin/Internal/Loader/LibraryModel/ManagedLibrary.cs @@ -6,67 +6,66 @@ using System.Diagnostics; using System.IO; using System.Reflection; -namespace Dalamud.Plugin.Internal.Loader.LibraryModel +namespace Dalamud.Plugin.Internal.Loader.LibraryModel; + +/// +/// Represents a managed, .NET assembly. +/// +[DebuggerDisplay("{Name} = {AdditionalProbingPath}")] +internal class ManagedLibrary { - /// - /// Represents a managed, .NET assembly. - /// - [DebuggerDisplay("{Name} = {AdditionalProbingPath}")] - internal class ManagedLibrary + private ManagedLibrary(AssemblyName name, string additionalProbingPath, string appLocalPath) { - private ManagedLibrary(AssemblyName name, string additionalProbingPath, string appLocalPath) - { - this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); - this.AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); - } + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); + this.AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); + } - /// - /// Gets the name of the managed library. - /// - public AssemblyName Name { get; } + /// + /// Gets the name of the managed library. + /// + public AssemblyName Name { get; } - /// - /// Gets the path to file within an additional probing path root. This is typically a combination - /// of the NuGet package ID (lowercased), version, and path within the package. - /// - /// For example, microsoft.data.sqlite/1.0.0/lib/netstandard1.3/Microsoft.Data.Sqlite.dll. - /// - /// - public string AdditionalProbingPath { get; } + /// + /// Gets the path to file within an additional probing path root. This is typically a combination + /// of the NuGet package ID (lowercased), version, and path within the package. + /// + /// For example, microsoft.data.sqlite/1.0.0/lib/netstandard1.3/Microsoft.Data.Sqlite.dll. + /// + /// + public string AdditionalProbingPath { get; } - /// - /// Gets the path to file within a deployed, framework-dependent application. - /// - /// For most managed libraries, this will be the file name. - /// For example, MyPlugin1.dll. - /// - /// - /// For runtime-specific managed implementations, this may include a sub folder path. - /// For example, runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.dll. - /// - /// - public string AppLocalPath { get; } + /// + /// Gets the path to file within a deployed, framework-dependent application. + /// + /// For most managed libraries, this will be the file name. + /// For example, MyPlugin1.dll. + /// + /// + /// For runtime-specific managed implementations, this may include a sub folder path. + /// For example, runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.dll. + /// + /// + public string AppLocalPath { get; } - /// - /// Create an instance of from a NuGet package. - /// - /// The name of the package. - /// The version of the package. - /// The path within the NuGet package. - /// A managed library. - public static ManagedLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) - { - // When the asset comes from "lib/$tfm/", Microsoft.NET.Sdk will flatten this during publish based on the most compatible TFM. - // The SDK will not flatten managed libraries found under runtimes/ - var appLocalPath = assetPath.StartsWith("lib/") - ? Path.GetFileName(assetPath) - : assetPath; + /// + /// Create an instance of from a NuGet package. + /// + /// The name of the package. + /// The version of the package. + /// The path within the NuGet package. + /// A managed library. + public static ManagedLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) + { + // When the asset comes from "lib/$tfm/", Microsoft.NET.Sdk will flatten this during publish based on the most compatible TFM. + // The SDK will not flatten managed libraries found under runtimes/ + var appLocalPath = assetPath.StartsWith("lib/") + ? Path.GetFileName(assetPath) + : assetPath; - return new ManagedLibrary( - new AssemblyName(Path.GetFileNameWithoutExtension(assetPath)), - Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath), - appLocalPath); - } + return new ManagedLibrary( + new AssemblyName(Path.GetFileNameWithoutExtension(assetPath)), + Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath), + appLocalPath); } } diff --git a/Dalamud/Plugin/Internal/Loader/LibraryModel/NativeLibrary.cs b/Dalamud/Plugin/Internal/Loader/LibraryModel/NativeLibrary.cs index 35d6eb3e8..47b9f701d 100644 --- a/Dalamud/Plugin/Internal/Loader/LibraryModel/NativeLibrary.cs +++ b/Dalamud/Plugin/Internal/Loader/LibraryModel/NativeLibrary.cs @@ -5,64 +5,63 @@ using System; using System.Diagnostics; using System.IO; -namespace Dalamud.Plugin.Internal.Loader.LibraryModel +namespace Dalamud.Plugin.Internal.Loader.LibraryModel; + +/// +/// Represents an unmanaged library, such as `libsqlite3`, which may need to be loaded +/// for P/Invoke to work. +/// +[DebuggerDisplay("{Name} = {AdditionalProbingPath}")] +internal class NativeLibrary { - /// - /// Represents an unmanaged library, such as `libsqlite3`, which may need to be loaded - /// for P/Invoke to work. - /// - [DebuggerDisplay("{Name} = {AdditionalProbingPath}")] - internal class NativeLibrary + private NativeLibrary(string name, string appLocalPath, string additionalProbingPath) { - private NativeLibrary(string name, string appLocalPath, string additionalProbingPath) - { - this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); - this.AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); - } + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); + this.AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); + } - /// - /// Gets the name of the native library. This should match the name of the P/Invoke call. - /// - /// For example, if specifying `[DllImport("sqlite3")]`, should be sqlite3. - /// This may not match the exact file name as loading will attempt variations on the name according - /// to OS convention. On Windows, P/Invoke will attempt to load `sqlite3.dll`. On macOS, it will - /// attempt to find `sqlite3.dylib` and `libsqlite3.dylib`. On Linux, it will attempt to find - /// `sqlite3.so` and `libsqlite3.so`. - /// - /// - public string Name { get; } + /// + /// Gets the name of the native library. This should match the name of the P/Invoke call. + /// + /// For example, if specifying `[DllImport("sqlite3")]`, should be sqlite3. + /// This may not match the exact file name as loading will attempt variations on the name according + /// to OS convention. On Windows, P/Invoke will attempt to load `sqlite3.dll`. On macOS, it will + /// attempt to find `sqlite3.dylib` and `libsqlite3.dylib`. On Linux, it will attempt to find + /// `sqlite3.so` and `libsqlite3.so`. + /// + /// + public string Name { get; } - /// - /// Gets the path to file within a deployed, framework-dependent application. - /// - /// For example, runtimes/linux-x64/native/libsqlite.so. - /// - /// - public string AppLocalPath { get; } + /// + /// Gets the path to file within a deployed, framework-dependent application. + /// + /// For example, runtimes/linux-x64/native/libsqlite.so. + /// + /// + public string AppLocalPath { get; } - /// - /// Gets the path to file within an additional probing path root. This is typically a combination - /// of the NuGet package ID (lowercased), version, and path within the package. - /// - /// For example, sqlite/3.13.3/runtimes/linux-x64/native/libsqlite.so. - /// - /// - public string AdditionalProbingPath { get; } + /// + /// Gets the path to file within an additional probing path root. This is typically a combination + /// of the NuGet package ID (lowercased), version, and path within the package. + /// + /// For example, sqlite/3.13.3/runtimes/linux-x64/native/libsqlite.so. + /// + /// + public string AdditionalProbingPath { get; } - /// - /// Create an instance of from a NuGet package. - /// - /// The name of the package. - /// The version of the package. - /// The path within the NuGet package. - /// A native library. - public static NativeLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) - { - return new NativeLibrary( - Path.GetFileNameWithoutExtension(assetPath), - assetPath, - Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath)); - } + /// + /// Create an instance of from a NuGet package. + /// + /// The name of the package. + /// The version of the package. + /// The path within the NuGet package. + /// A native library. + public static NativeLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) + { + return new NativeLibrary( + Path.GetFileNameWithoutExtension(assetPath), + assetPath, + Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath)); } } diff --git a/Dalamud/Plugin/Internal/Loader/LoaderConfig.cs b/Dalamud/Plugin/Internal/Loader/LoaderConfig.cs index 90cf44d07..d3fcdc99e 100644 --- a/Dalamud/Plugin/Internal/Loader/LoaderConfig.cs +++ b/Dalamud/Plugin/Internal/Loader/LoaderConfig.cs @@ -7,71 +7,70 @@ using System.IO; using System.Reflection; using System.Runtime.Loader; -namespace Dalamud.Plugin.Internal.Loader +namespace Dalamud.Plugin.Internal.Loader; + +/// +/// Represents the configuration for a plugin loader. +/// +internal class LoaderConfig { /// - /// Represents the configuration for a plugin loader. + /// Initializes a new instance of the class. /// - internal class LoaderConfig + /// The full file path to the main assembly for the plugin. + public LoaderConfig(string mainAssemblyPath) { - /// - /// Initializes a new instance of the class. - /// - /// The full file path to the main assembly for the plugin. - public LoaderConfig(string mainAssemblyPath) - { - if (string.IsNullOrEmpty(mainAssemblyPath)) - throw new ArgumentException("Value must be null or not empty", nameof(mainAssemblyPath)); + if (string.IsNullOrEmpty(mainAssemblyPath)) + throw new ArgumentException("Value must be null or not empty", nameof(mainAssemblyPath)); - if (!Path.IsPathRooted(mainAssemblyPath)) - throw new ArgumentException("Value must be an absolute file path", nameof(mainAssemblyPath)); + if (!Path.IsPathRooted(mainAssemblyPath)) + throw new ArgumentException("Value must be an absolute file path", nameof(mainAssemblyPath)); - if (!File.Exists(mainAssemblyPath)) - throw new ArgumentException("Value must exist", nameof(mainAssemblyPath)); + if (!File.Exists(mainAssemblyPath)) + throw new ArgumentException("Value must exist", nameof(mainAssemblyPath)); - this.MainAssemblyPath = mainAssemblyPath; - } - - /// - /// Gets the file path to the main assembly. - /// - public string MainAssemblyPath { get; } - - /// - /// Gets a list of assemblies which should be treated as private. - /// - public ICollection PrivateAssemblies { get; } = new List(); - - /// - /// Gets a list of assemblies which should be unified between the host and the plugin. - /// - /// what-are-shared-types - public ICollection SharedAssemblies { get; } = new List(); - - /// - /// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host. - /// - /// This does not guarantee types will unify. - /// - /// what-are-shared-types - /// - public bool PreferSharedTypes { get; set; } - - /// - /// Gets or sets the default used by the . - /// Use this feature if the of the is not the Runtime's default load context. - /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != . - /// - public AssemblyLoadContext DefaultContext { get; set; } = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; - - /// - /// Gets or sets a value indicating whether the plugin can be unloaded from memory. - /// - public bool IsUnloadable { get; set; } - - /// - /// Gets or sets a value indicating whether to load assemblies into memory in order to not lock files. - /// - public bool LoadInMemory { get; set; } + this.MainAssemblyPath = mainAssemblyPath; } + + /// + /// Gets the file path to the main assembly. + /// + public string MainAssemblyPath { get; } + + /// + /// Gets a list of assemblies which should be treated as private. + /// + public ICollection PrivateAssemblies { get; } = new List(); + + /// + /// Gets a list of assemblies which should be unified between the host and the plugin. + /// + /// what-are-shared-types + public ICollection SharedAssemblies { get; } = new List(); + + /// + /// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host. + /// + /// This does not guarantee types will unify. + /// + /// what-are-shared-types + /// + public bool PreferSharedTypes { get; set; } + + /// + /// Gets or sets the default used by the . + /// Use this feature if the of the is not the Runtime's default load context. + /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != . + /// + public AssemblyLoadContext DefaultContext { get; set; } = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; + + /// + /// Gets or sets a value indicating whether the plugin can be unloaded from memory. + /// + public bool IsUnloadable { get; set; } + + /// + /// Gets or sets a value indicating whether to load assemblies into memory in order to not lock files. + /// + public bool LoadInMemory { get; set; } } diff --git a/Dalamud/Plugin/Internal/Loader/ManagedLoadContext.cs b/Dalamud/Plugin/Internal/Loader/ManagedLoadContext.cs index 26bc3d7d3..4bb326ce4 100644 --- a/Dalamud/Plugin/Internal/Loader/ManagedLoadContext.cs +++ b/Dalamud/Plugin/Internal/Loader/ManagedLoadContext.cs @@ -11,387 +11,386 @@ using System.Runtime.Loader; using Dalamud.Plugin.Internal.Loader.LibraryModel; -namespace Dalamud.Plugin.Internal.Loader +namespace Dalamud.Plugin.Internal.Loader; + +/// +/// An implementation of which attempts to load managed and native +/// binaries at runtime immitating some of the behaviors of corehost. +/// +[DebuggerDisplay("'{Name}' ({_mainAssemblyPath})")] +internal class ManagedLoadContext : AssemblyLoadContext { + private readonly string basePath; + private readonly string mainAssemblyPath; + private readonly IReadOnlyDictionary managedAssemblies; + private readonly IReadOnlyDictionary nativeLibraries; + private readonly IReadOnlyCollection privateAssemblies; + private readonly ICollection defaultAssemblies; + private readonly IReadOnlyCollection additionalProbingPaths; + private readonly bool preferDefaultLoadContext; + private readonly string[] resourceRoots; + private readonly bool loadInMemory; + private readonly AssemblyLoadContext defaultLoadContext; + private readonly AssemblyDependencyResolver dependencyResolver; + private readonly bool shadowCopyNativeLibraries; + private readonly string unmanagedDllShadowCopyDirectoryPath; + /// - /// An implementation of which attempts to load managed and native - /// binaries at runtime immitating some of the behaviors of corehost. + /// Initializes a new instance of the class. /// - [DebuggerDisplay("'{Name}' ({_mainAssemblyPath})")] - internal class ManagedLoadContext : AssemblyLoadContext + /// Main assembly path. + /// Managed assemblies. + /// Native assemblies. + /// Private assemblies. + /// Default assemblies. + /// Additional probing paths. + /// Resource probing paths. + /// Default load context. + /// If the default load context should be prefered. + /// If the dll is collectible. + /// If the dll should be loaded in memory. + /// If native libraries should be shadow copied. + public ManagedLoadContext( + string mainAssemblyPath, + IReadOnlyDictionary managedAssemblies, + IReadOnlyDictionary nativeLibraries, + IReadOnlyCollection privateAssemblies, + IReadOnlyCollection defaultAssemblies, + IReadOnlyCollection additionalProbingPaths, + IReadOnlyCollection resourceProbingPaths, + AssemblyLoadContext defaultLoadContext, + bool preferDefaultLoadContext, + bool isCollectible, + bool loadInMemory, + bool shadowCopyNativeLibraries) + : base(Path.GetFileNameWithoutExtension(mainAssemblyPath), isCollectible) { - private readonly string basePath; - private readonly string mainAssemblyPath; - private readonly IReadOnlyDictionary managedAssemblies; - private readonly IReadOnlyDictionary nativeLibraries; - private readonly IReadOnlyCollection privateAssemblies; - private readonly ICollection defaultAssemblies; - private readonly IReadOnlyCollection additionalProbingPaths; - private readonly bool preferDefaultLoadContext; - private readonly string[] resourceRoots; - private readonly bool loadInMemory; - private readonly AssemblyLoadContext defaultLoadContext; - private readonly AssemblyDependencyResolver dependencyResolver; - private readonly bool shadowCopyNativeLibraries; - private readonly string unmanagedDllShadowCopyDirectoryPath; + if (resourceProbingPaths == null) + throw new ArgumentNullException(nameof(resourceProbingPaths)); - /// - /// Initializes a new instance of the class. - /// - /// Main assembly path. - /// Managed assemblies. - /// Native assemblies. - /// Private assemblies. - /// Default assemblies. - /// Additional probing paths. - /// Resource probing paths. - /// Default load context. - /// If the default load context should be prefered. - /// If the dll is collectible. - /// If the dll should be loaded in memory. - /// If native libraries should be shadow copied. - public ManagedLoadContext( - string mainAssemblyPath, - IReadOnlyDictionary managedAssemblies, - IReadOnlyDictionary nativeLibraries, - IReadOnlyCollection privateAssemblies, - IReadOnlyCollection defaultAssemblies, - IReadOnlyCollection additionalProbingPaths, - IReadOnlyCollection resourceProbingPaths, - AssemblyLoadContext defaultLoadContext, - bool preferDefaultLoadContext, - bool isCollectible, - bool loadInMemory, - bool shadowCopyNativeLibraries) - : base(Path.GetFileNameWithoutExtension(mainAssemblyPath), isCollectible) + this.mainAssemblyPath = mainAssemblyPath ?? throw new ArgumentNullException(nameof(mainAssemblyPath)); + this.dependencyResolver = new AssemblyDependencyResolver(mainAssemblyPath); + this.basePath = Path.GetDirectoryName(mainAssemblyPath) ?? throw new ArgumentException("Invalid assembly path", nameof(mainAssemblyPath)); + this.managedAssemblies = managedAssemblies ?? throw new ArgumentNullException(nameof(managedAssemblies)); + this.privateAssemblies = privateAssemblies ?? throw new ArgumentNullException(nameof(privateAssemblies)); + this.defaultAssemblies = defaultAssemblies != null ? defaultAssemblies.ToList() : throw new ArgumentNullException(nameof(defaultAssemblies)); + this.nativeLibraries = nativeLibraries ?? throw new ArgumentNullException(nameof(nativeLibraries)); + this.additionalProbingPaths = additionalProbingPaths ?? throw new ArgumentNullException(nameof(additionalProbingPaths)); + this.defaultLoadContext = defaultLoadContext; + this.preferDefaultLoadContext = preferDefaultLoadContext; + this.loadInMemory = loadInMemory; + + this.resourceRoots = new[] { this.basePath } + .Concat(resourceProbingPaths) + .ToArray(); + + this.shadowCopyNativeLibraries = shadowCopyNativeLibraries; + this.unmanagedDllShadowCopyDirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + if (shadowCopyNativeLibraries) { - if (resourceProbingPaths == null) - throw new ArgumentNullException(nameof(resourceProbingPaths)); + this.Unloading += _ => this.OnUnloaded(); + } + } - this.mainAssemblyPath = mainAssemblyPath ?? throw new ArgumentNullException(nameof(mainAssemblyPath)); - this.dependencyResolver = new AssemblyDependencyResolver(mainAssemblyPath); - this.basePath = Path.GetDirectoryName(mainAssemblyPath) ?? throw new ArgumentException("Invalid assembly path", nameof(mainAssemblyPath)); - this.managedAssemblies = managedAssemblies ?? throw new ArgumentNullException(nameof(managedAssemblies)); - this.privateAssemblies = privateAssemblies ?? throw new ArgumentNullException(nameof(privateAssemblies)); - this.defaultAssemblies = defaultAssemblies != null ? defaultAssemblies.ToList() : throw new ArgumentNullException(nameof(defaultAssemblies)); - this.nativeLibraries = nativeLibraries ?? throw new ArgumentNullException(nameof(nativeLibraries)); - this.additionalProbingPaths = additionalProbingPaths ?? throw new ArgumentNullException(nameof(additionalProbingPaths)); - this.defaultLoadContext = defaultLoadContext; - this.preferDefaultLoadContext = preferDefaultLoadContext; - this.loadInMemory = loadInMemory; + /// + /// Load an assembly from a filepath. + /// + /// Assembly path. + /// A loaded assembly. + public Assembly LoadAssemblyFromFilePath(string path) + { + if (!this.loadInMemory) + return this.LoadFromAssemblyPath(path); - this.resourceRoots = new[] { this.basePath } - .Concat(resourceProbingPaths) - .ToArray(); + using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); - this.shadowCopyNativeLibraries = shadowCopyNativeLibraries; - this.unmanagedDllShadowCopyDirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + var pdbPath = Path.ChangeExtension(path, ".pdb"); + if (File.Exists(pdbPath)) + { + using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return this.LoadFromStream(file, pdbFile); + } - if (shadowCopyNativeLibraries) + return this.LoadFromStream(file); + } + + /// + /// Load an assembly. + /// + /// Name of the assembly. + /// Loaded assembly. + protected override Assembly? Load(AssemblyName assemblyName) + { + if (assemblyName.Name == null) + { + // not sure how to handle this case. It's technically possible. + return null; + } + + if ((this.preferDefaultLoadContext || this.defaultAssemblies.Contains(assemblyName.Name)) && !this.privateAssemblies.Contains(assemblyName.Name)) + { + // If default context is preferred, check first for types in the default context unless the dependency has been declared as private + try { - this.Unloading += _ => this.OnUnloaded(); + var defaultAssembly = this.defaultLoadContext.LoadFromAssemblyName(assemblyName); + if (defaultAssembly != null) + { + // Older versions used to return null here such that returned assembly would be resolved from the default ALC. + // However, with the addition of custom default ALCs, the Default ALC may not be the user's chosen ALC when + // this context was built. As such, we simply return the Assembly from the user's chosen default load context. + return defaultAssembly; + } + } + catch + { + // Swallow errors in loading from the default context } } - /// - /// Load an assembly from a filepath. - /// - /// Assembly path. - /// A loaded assembly. - public Assembly LoadAssemblyFromFilePath(string path) + var resolvedPath = this.dependencyResolver.ResolveAssemblyToPath(assemblyName); + if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) { - if (!this.loadInMemory) - return this.LoadFromAssemblyPath(path); - - using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); - - var pdbPath = Path.ChangeExtension(path, ".pdb"); - if (File.Exists(pdbPath)) - { - using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); - return this.LoadFromStream(file, pdbFile); - } - - return this.LoadFromStream(file); + return this.LoadAssemblyFromFilePath(resolvedPath); } - /// - /// Load an assembly. - /// - /// Name of the assembly. - /// Loaded assembly. - protected override Assembly? Load(AssemblyName assemblyName) + // Resource assembly binding does not use the TPA. Instead, it probes PLATFORM_RESOURCE_ROOTS (a list of folders) + // for $folder/$culture/$assemblyName.dll + // See https://github.com/dotnet/coreclr/blob/3fca50a36e62a7433d7601d805d38de6baee7951/src/binder/assemblybinder.cpp#L1232-L1290 + + if (!string.IsNullOrEmpty(assemblyName.CultureName) && !string.Equals(assemblyName.CultureName, "neutral")) { - if (assemblyName.Name == null) + foreach (var resourceRoot in this.resourceRoots) { - // not sure how to handle this case. It's technically possible. - return null; - } - - if ((this.preferDefaultLoadContext || this.defaultAssemblies.Contains(assemblyName.Name)) && !this.privateAssemblies.Contains(assemblyName.Name)) - { - // If default context is preferred, check first for types in the default context unless the dependency has been declared as private - try + var resourcePath = Path.Combine(resourceRoot, assemblyName.CultureName, assemblyName.Name + ".dll"); + if (File.Exists(resourcePath)) { - var defaultAssembly = this.defaultLoadContext.LoadFromAssemblyName(assemblyName); - if (defaultAssembly != null) - { - // Older versions used to return null here such that returned assembly would be resolved from the default ALC. - // However, with the addition of custom default ALCs, the Default ALC may not be the user's chosen ALC when - // this context was built. As such, we simply return the Assembly from the user's chosen default load context. - return defaultAssembly; - } - } - catch - { - // Swallow errors in loading from the default context - } - } - - var resolvedPath = this.dependencyResolver.ResolveAssemblyToPath(assemblyName); - if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) - { - return this.LoadAssemblyFromFilePath(resolvedPath); - } - - // Resource assembly binding does not use the TPA. Instead, it probes PLATFORM_RESOURCE_ROOTS (a list of folders) - // for $folder/$culture/$assemblyName.dll - // See https://github.com/dotnet/coreclr/blob/3fca50a36e62a7433d7601d805d38de6baee7951/src/binder/assemblybinder.cpp#L1232-L1290 - - if (!string.IsNullOrEmpty(assemblyName.CultureName) && !string.Equals(assemblyName.CultureName, "neutral")) - { - foreach (var resourceRoot in this.resourceRoots) - { - var resourcePath = Path.Combine(resourceRoot, assemblyName.CultureName, assemblyName.Name + ".dll"); - if (File.Exists(resourcePath)) - { - return this.LoadAssemblyFromFilePath(resourcePath); - } - } - - return null; - } - - if (this.managedAssemblies.TryGetValue(assemblyName.Name, out var library) && library != null) - { - if (this.SearchForLibrary(library, out var path) && path != null) - { - return this.LoadAssemblyFromFilePath(path); - } - } - else - { - // if an assembly was not listed in the list of known assemblies, - // fallback to the load context base directory - var dllName = assemblyName.Name + ".dll"; - foreach (var probingPath in this.additionalProbingPaths.Prepend(this.basePath)) - { - var localFile = Path.Combine(probingPath, dllName); - if (File.Exists(localFile)) - { - return this.LoadAssemblyFromFilePath(localFile); - } + return this.LoadAssemblyFromFilePath(resourcePath); } } return null; } - /// - /// Loads the unmanaged binary using configured list of native libraries. - /// - /// Unmanaged DLL name. - /// The unmanaged dll handle. - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + if (this.managedAssemblies.TryGetValue(assemblyName.Name, out var library) && library != null) { - var resolvedPath = this.dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) + if (this.SearchForLibrary(library, out var path) && path != null) { - return this.LoadUnmanagedDllFromResolvedPath(resolvedPath, normalizePath: false); + return this.LoadAssemblyFromFilePath(path); } - - foreach (var prefix in PlatformInformation.NativeLibraryPrefixes) + } + else + { + // if an assembly was not listed in the list of known assemblies, + // fallback to the load context base directory + var dllName = assemblyName.Name + ".dll"; + foreach (var probingPath in this.additionalProbingPaths.Prepend(this.basePath)) { - if (this.nativeLibraries.TryGetValue(prefix + unmanagedDllName, out var library)) + var localFile = Path.Combine(probingPath, dllName); + if (File.Exists(localFile)) { - if (this.SearchForLibrary(library, prefix, out var path) && path != null) - { - return this.LoadUnmanagedDllFromResolvedPath(path); - } - } - else - { - // coreclr allows code to use [DllImport("sni")] or [DllImport("sni.dll")] - // This library treats the file name without the extension as the lookup name, - // so this loop is necessary to check if the unmanaged name matches a library - // when the file extension has been trimmed. - foreach (var suffix in PlatformInformation.NativeLibraryExtensions) - { - if (!unmanagedDllName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - // check to see if there is a library entry for the library without the file extension - var trimmedName = unmanagedDllName.Substring(0, unmanagedDllName.Length - suffix.Length); - - if (this.nativeLibraries.TryGetValue(prefix + trimmedName, out library)) - { - if (this.SearchForLibrary(library, prefix, out var path) && path != null) - { - return this.LoadUnmanagedDllFromResolvedPath(path); - } - } - else - { - // fallback to native assets which match the file name in the plugin base directory - var prefixSuffixDllName = prefix + unmanagedDllName + suffix; - var prefixDllName = prefix + unmanagedDllName; - - foreach (var probingPath in this.additionalProbingPaths.Prepend(this.basePath)) - { - var localFile = Path.Combine(probingPath, prefixSuffixDllName); - if (File.Exists(localFile)) - { - return this.LoadUnmanagedDllFromResolvedPath(localFile); - } - - var localFileWithoutSuffix = Path.Combine(probingPath, prefixDllName); - if (File.Exists(localFileWithoutSuffix)) - { - return this.LoadUnmanagedDllFromResolvedPath(localFileWithoutSuffix); - } - } - } - } + return this.LoadAssemblyFromFilePath(localFile); } } - - return base.LoadUnmanagedDll(unmanagedDllName); } - private bool SearchForLibrary(ManagedLibrary library, out string? path) + return null; + } + + /// + /// Loads the unmanaged binary using configured list of native libraries. + /// + /// Unmanaged DLL name. + /// The unmanaged dll handle. + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + var resolvedPath = this.dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) { - // 1. Check for in _basePath + app local path - var localFile = Path.Combine(this.basePath, library.AppLocalPath); - if (File.Exists(localFile)) + return this.LoadUnmanagedDllFromResolvedPath(resolvedPath, normalizePath: false); + } + + foreach (var prefix in PlatformInformation.NativeLibraryPrefixes) + { + if (this.nativeLibraries.TryGetValue(prefix + unmanagedDllName, out var library)) { - path = localFile; + if (this.SearchForLibrary(library, prefix, out var path) && path != null) + { + return this.LoadUnmanagedDllFromResolvedPath(path); + } + } + else + { + // coreclr allows code to use [DllImport("sni")] or [DllImport("sni.dll")] + // This library treats the file name without the extension as the lookup name, + // so this loop is necessary to check if the unmanaged name matches a library + // when the file extension has been trimmed. + foreach (var suffix in PlatformInformation.NativeLibraryExtensions) + { + if (!unmanagedDllName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + // check to see if there is a library entry for the library without the file extension + var trimmedName = unmanagedDllName.Substring(0, unmanagedDllName.Length - suffix.Length); + + if (this.nativeLibraries.TryGetValue(prefix + trimmedName, out library)) + { + if (this.SearchForLibrary(library, prefix, out var path) && path != null) + { + return this.LoadUnmanagedDllFromResolvedPath(path); + } + } + else + { + // fallback to native assets which match the file name in the plugin base directory + var prefixSuffixDllName = prefix + unmanagedDllName + suffix; + var prefixDllName = prefix + unmanagedDllName; + + foreach (var probingPath in this.additionalProbingPaths.Prepend(this.basePath)) + { + var localFile = Path.Combine(probingPath, prefixSuffixDllName); + if (File.Exists(localFile)) + { + return this.LoadUnmanagedDllFromResolvedPath(localFile); + } + + var localFileWithoutSuffix = Path.Combine(probingPath, prefixDllName); + if (File.Exists(localFileWithoutSuffix)) + { + return this.LoadUnmanagedDllFromResolvedPath(localFileWithoutSuffix); + } + } + } + } + } + } + + return base.LoadUnmanagedDll(unmanagedDllName); + } + + private bool SearchForLibrary(ManagedLibrary library, out string? path) + { + // 1. Check for in _basePath + app local path + var localFile = Path.Combine(this.basePath, library.AppLocalPath); + if (File.Exists(localFile)) + { + path = localFile; + return true; + } + + // 2. Search additional probing paths + foreach (var searchPath in this.additionalProbingPaths) + { + var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); + if (File.Exists(candidate)) + { + path = candidate; return true; } - - // 2. Search additional probing paths - foreach (var searchPath in this.additionalProbingPaths) - { - var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); - if (File.Exists(candidate)) - { - path = candidate; - return true; - } - } - - // 3. Search in base path - foreach (var ext in PlatformInformation.ManagedAssemblyExtensions) - { - var local = Path.Combine(this.basePath, library.Name.Name + ext); - if (File.Exists(local)) - { - path = local; - return true; - } - } - - path = null; - return false; } - private bool SearchForLibrary(NativeLibrary library, string prefix, out string? path) + // 3. Search in base path + foreach (var ext in PlatformInformation.ManagedAssemblyExtensions) { - // 1. Search in base path - foreach (var ext in PlatformInformation.NativeLibraryExtensions) - { - var candidate = Path.Combine(this.basePath, $"{prefix}{library.Name}{ext}"); - if (File.Exists(candidate)) - { - path = candidate; - return true; - } - } - - // 2. Search in base path + app local (for portable deployments of netcoreapp) - var local = Path.Combine(this.basePath, library.AppLocalPath); + var local = Path.Combine(this.basePath, library.Name.Name + ext); if (File.Exists(local)) { path = local; return true; } - - // 3. Search additional probing paths - foreach (var searchPath in this.additionalProbingPaths) - { - var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); - if (File.Exists(candidate)) - { - path = candidate; - return true; - } - } - - path = null; - return false; } - private IntPtr LoadUnmanagedDllFromResolvedPath(string unmanagedDllPath, bool normalizePath = true) - { - if (normalizePath) - { - unmanagedDllPath = Path.GetFullPath(unmanagedDllPath); - } + path = null; + return false; + } - return this.shadowCopyNativeLibraries - ? this.LoadUnmanagedDllFromShadowCopy(unmanagedDllPath) - : this.LoadUnmanagedDllFromPath(unmanagedDllPath); + private bool SearchForLibrary(NativeLibrary library, string prefix, out string? path) + { + // 1. Search in base path + foreach (var ext in PlatformInformation.NativeLibraryExtensions) + { + var candidate = Path.Combine(this.basePath, $"{prefix}{library.Name}{ext}"); + if (File.Exists(candidate)) + { + path = candidate; + return true; + } } - private IntPtr LoadUnmanagedDllFromShadowCopy(string unmanagedDllPath) + // 2. Search in base path + app local (for portable deployments of netcoreapp) + var local = Path.Combine(this.basePath, library.AppLocalPath); + if (File.Exists(local)) { - var shadowCopyDllPath = this.CreateShadowCopy(unmanagedDllPath); - - return this.LoadUnmanagedDllFromPath(shadowCopyDllPath); + path = local; + return true; } - private string CreateShadowCopy(string dllPath) + // 3. Search additional probing paths + foreach (var searchPath in this.additionalProbingPaths) { - Directory.CreateDirectory(this.unmanagedDllShadowCopyDirectoryPath); - - var dllFileName = Path.GetFileName(dllPath); - var shadowCopyPath = Path.Combine(this.unmanagedDllShadowCopyDirectoryPath, dllFileName); - - if (!File.Exists(shadowCopyPath)) + var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); + if (File.Exists(candidate)) { - File.Copy(dllPath, shadowCopyPath); + path = candidate; + return true; } - - return shadowCopyPath; } - private void OnUnloaded() - { - if (!this.shadowCopyNativeLibraries || !Directory.Exists(this.unmanagedDllShadowCopyDirectoryPath)) - { - return; - } + path = null; + return false; + } - // Attempt to delete shadow copies - try - { - Directory.Delete(this.unmanagedDllShadowCopyDirectoryPath, recursive: true); - } - catch (Exception) - { - // Files might be locked by host process. Nothing we can do about it, I guess. - } + private IntPtr LoadUnmanagedDllFromResolvedPath(string unmanagedDllPath, bool normalizePath = true) + { + if (normalizePath) + { + unmanagedDllPath = Path.GetFullPath(unmanagedDllPath); + } + + return this.shadowCopyNativeLibraries + ? this.LoadUnmanagedDllFromShadowCopy(unmanagedDllPath) + : this.LoadUnmanagedDllFromPath(unmanagedDllPath); + } + + private IntPtr LoadUnmanagedDllFromShadowCopy(string unmanagedDllPath) + { + var shadowCopyDllPath = this.CreateShadowCopy(unmanagedDllPath); + + return this.LoadUnmanagedDllFromPath(shadowCopyDllPath); + } + + private string CreateShadowCopy(string dllPath) + { + Directory.CreateDirectory(this.unmanagedDllShadowCopyDirectoryPath); + + var dllFileName = Path.GetFileName(dllPath); + var shadowCopyPath = Path.Combine(this.unmanagedDllShadowCopyDirectoryPath, dllFileName); + + if (!File.Exists(shadowCopyPath)) + { + File.Copy(dllPath, shadowCopyPath); + } + + return shadowCopyPath; + } + + private void OnUnloaded() + { + if (!this.shadowCopyNativeLibraries || !Directory.Exists(this.unmanagedDllShadowCopyDirectoryPath)) + { + return; + } + + // Attempt to delete shadow copies + try + { + Directory.Delete(this.unmanagedDllShadowCopyDirectoryPath, recursive: true); + } + catch (Exception) + { + // Files might be locked by host process. Nothing we can do about it, I guess. } } } diff --git a/Dalamud/Plugin/Internal/Loader/PlatformInformation.cs b/Dalamud/Plugin/Internal/Loader/PlatformInformation.cs index 47a3d7acf..ec1d557be 100644 --- a/Dalamud/Plugin/Internal/Loader/PlatformInformation.cs +++ b/Dalamud/Plugin/Internal/Loader/PlatformInformation.cs @@ -1,32 +1,31 @@ // Copyright (c) Nate McMaster, Dalamud team. // Licensed under the Apache License, Version 2.0. See License.txt in the Loader root for license information. -namespace Dalamud.Plugin.Internal.Loader +namespace Dalamud.Plugin.Internal.Loader; + +/// +/// Platform specific information. +/// +internal class PlatformInformation { /// - /// Platform specific information. + /// Gets a list of native OS specific library extensions. /// - internal class PlatformInformation + public static string[] NativeLibraryExtensions => new[] { ".dll" }; + + /// + /// Gets a list of native OS specific library prefixes. + /// + public static string[] NativeLibraryPrefixes => new[] { string.Empty }; + + /// + /// Gets a list of native OS specific managed assembly extensions. + /// + public static string[] ManagedAssemblyExtensions => new[] { - /// - /// Gets a list of native OS specific library extensions. - /// - public static string[] NativeLibraryExtensions => new[] { ".dll" }; - - /// - /// Gets a list of native OS specific library prefixes. - /// - public static string[] NativeLibraryPrefixes => new[] { string.Empty }; - - /// - /// Gets a list of native OS specific managed assembly extensions. - /// - public static string[] ManagedAssemblyExtensions => new[] - { - ".dll", - ".ni.dll", - ".exe", - ".ni.exe", - }; - } + ".dll", + ".ni.dll", + ".exe", + ".ni.exe", + }; } diff --git a/Dalamud/Plugin/Internal/Loader/PluginLoader.cs b/Dalamud/Plugin/Internal/Loader/PluginLoader.cs index 020739f3a..5c03c32b8 100644 --- a/Dalamud/Plugin/Internal/Loader/PluginLoader.cs +++ b/Dalamud/Plugin/Internal/Loader/PluginLoader.cs @@ -5,159 +5,158 @@ using System; using System.Reflection; using System.Runtime.Loader; -namespace Dalamud.Plugin.Internal.Loader +namespace Dalamud.Plugin.Internal.Loader; + +/// +/// This loader attempts to load binaries for execution (both managed assemblies and native libraries) +/// in the same way that .NET Core would if they were originally part of the .NET Core application. +/// +/// This loader reads configuration files produced by .NET Core (.deps.json and runtimeconfig.json) +/// as well as a custom file (*.config files). These files describe a list of .dlls and a set of dependencies. +/// The loader searches the plugin path, as well as any additionally specified paths, for binaries +/// which satisfy the plugin's requirements. +/// +/// +internal class PluginLoader : IDisposable { + private readonly LoaderConfig config; + private readonly AssemblyLoadContextBuilder contextBuilder; + private ManagedLoadContext context; + private volatile bool disposed; + /// - /// This loader attempts to load binaries for execution (both managed assemblies and native libraries) - /// in the same way that .NET Core would if they were originally part of the .NET Core application. + /// Initializes a new instance of the class. + /// + /// The configuration for the plugin. + public PluginLoader(LoaderConfig config) + { + this.config = config ?? throw new ArgumentNullException(nameof(config)); + this.contextBuilder = CreateLoadContextBuilder(config); + this.context = (ManagedLoadContext)this.contextBuilder.Build(); + } + + /// + /// Gets a value indicating whether this plugin is capable of being unloaded. + /// + public bool IsUnloadable + => this.context.IsCollectible; + + /// + /// Gets the assembly load context. + /// + public AssemblyLoadContext LoadContext => this.context; + + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// A function which can be used to configure advanced options for the plugin loader. + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Action configure) + { + if (configure == null) + throw new ArgumentNullException(nameof(configure)); + + var config = new LoaderConfig(assemblyFile); + configure(config); + return new PluginLoader(config); + } + + /// + /// The unloads and reloads the plugin assemblies. + /// This method throws if is false. + /// + public void Reload() + { + this.EnsureNotDisposed(); + + if (!this.IsUnloadable) + { + throw new InvalidOperationException("Reload cannot be used because IsUnloadable is false"); + } + + this.context.Unload(); + this.context = (ManagedLoadContext)this.contextBuilder.Build(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + /// + /// Load the main assembly for the plugin. + /// + /// The assembly. + public Assembly LoadDefaultAssembly() + { + this.EnsureNotDisposed(); + return this.context.LoadAssemblyFromFilePath(this.config.MainAssemblyPath); + } + + /// + /// Sets the scope used by some System.Reflection APIs which might trigger assembly loading. /// - /// This loader reads configuration files produced by .NET Core (.deps.json and runtimeconfig.json) - /// as well as a custom file (*.config files). These files describe a list of .dlls and a set of dependencies. - /// The loader searches the plugin path, as well as any additionally specified paths, for binaries - /// which satisfy the plugin's requirements. + /// See https://github.com/dotnet/coreclr/blob/v3.0.0/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md for more details. /// /// - internal class PluginLoader : IDisposable + /// A contextual reflection scope. + public AssemblyLoadContext.ContextualReflectionScope EnterContextualReflection() + => this.context.EnterContextualReflection(); + + /// + /// Disposes the plugin loader. This only does something if is true. + /// When true, this will unload assemblies which which were loaded during the lifetime + /// of the plugin. + /// + public void Dispose() { - private readonly LoaderConfig config; - private readonly AssemblyLoadContextBuilder contextBuilder; - private ManagedLoadContext context; - private volatile bool disposed; + if (this.disposed) + return; - /// - /// Initializes a new instance of the class. - /// - /// The configuration for the plugin. - public PluginLoader(LoaderConfig config) - { - this.config = config ?? throw new ArgumentNullException(nameof(config)); - this.contextBuilder = CreateLoadContextBuilder(config); - this.context = (ManagedLoadContext)this.contextBuilder.Build(); - } - - /// - /// Gets a value indicating whether this plugin is capable of being unloaded. - /// - public bool IsUnloadable - => this.context.IsCollectible; - - /// - /// Gets the assembly load context. - /// - public AssemblyLoadContext LoadContext => this.context; - - /// - /// Create a plugin loader for an assembly file. - /// - /// The file path to the main assembly for the plugin. - /// A function which can be used to configure advanced options for the plugin loader. - /// A loader. - public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Action configure) - { - if (configure == null) - throw new ArgumentNullException(nameof(configure)); - - var config = new LoaderConfig(assemblyFile); - configure(config); - return new PluginLoader(config); - } - - /// - /// The unloads and reloads the plugin assemblies. - /// This method throws if is false. - /// - public void Reload() - { - this.EnsureNotDisposed(); - - if (!this.IsUnloadable) - { - throw new InvalidOperationException("Reload cannot be used because IsUnloadable is false"); - } + this.disposed = true; + if (this.context.IsCollectible) this.context.Unload(); - this.context = (ManagedLoadContext)this.contextBuilder.Build(); + } - GC.Collect(); - GC.WaitForPendingFinalizers(); - } + private static AssemblyLoadContextBuilder CreateLoadContextBuilder(LoaderConfig config) + { + var builder = new AssemblyLoadContextBuilder(); - /// - /// Load the main assembly for the plugin. - /// - /// The assembly. - public Assembly LoadDefaultAssembly() + builder.SetMainAssemblyPath(config.MainAssemblyPath); + builder.SetDefaultContext(config.DefaultContext); + + foreach (var ext in config.PrivateAssemblies) { - this.EnsureNotDisposed(); - return this.context.LoadAssemblyFromFilePath(this.config.MainAssemblyPath); + builder.PreferLoadContextAssembly(ext); } - /// - /// Sets the scope used by some System.Reflection APIs which might trigger assembly loading. - /// - /// See https://github.com/dotnet/coreclr/blob/v3.0.0/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md for more details. - /// - /// - /// A contextual reflection scope. - public AssemblyLoadContext.ContextualReflectionScope EnterContextualReflection() - => this.context.EnterContextualReflection(); - - /// - /// Disposes the plugin loader. This only does something if is true. - /// When true, this will unload assemblies which which were loaded during the lifetime - /// of the plugin. - /// - public void Dispose() + if (config.PreferSharedTypes) { - if (this.disposed) - return; - - this.disposed = true; - - if (this.context.IsCollectible) - this.context.Unload(); + builder.PreferDefaultLoadContext(true); } - private static AssemblyLoadContextBuilder CreateLoadContextBuilder(LoaderConfig config) + if (config.IsUnloadable) { - var builder = new AssemblyLoadContextBuilder(); - - builder.SetMainAssemblyPath(config.MainAssemblyPath); - builder.SetDefaultContext(config.DefaultContext); - - foreach (var ext in config.PrivateAssemblies) - { - builder.PreferLoadContextAssembly(ext); - } - - if (config.PreferSharedTypes) - { - builder.PreferDefaultLoadContext(true); - } - - if (config.IsUnloadable) - { - builder.EnableUnloading(); - } - - if (config.LoadInMemory) - { - builder.PreloadAssembliesIntoMemory(); - builder.ShadowCopyNativeLibraries(); - } - - foreach (var assemblyName in config.SharedAssemblies) - { - builder.PreferDefaultLoadContextAssembly(assemblyName); - } - - return builder; + builder.EnableUnloading(); } - private void EnsureNotDisposed() + if (config.LoadInMemory) { - if (this.disposed) - throw new ObjectDisposedException(nameof(PluginLoader)); + builder.PreloadAssembliesIntoMemory(); + builder.ShadowCopyNativeLibraries(); } + + foreach (var assemblyName in config.SharedAssemblies) + { + builder.PreferDefaultLoadContextAssembly(assemblyName); + } + + return builder; + } + + private void EnsureNotDisposed() + { + if (this.disposed) + throw new ObjectDisposedException(nameof(PluginLoader)); } } 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 2a857b2d0..7d0f90cb6 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGate.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGate.cs @@ -1,30 +1,29 @@ using System.Collections.Generic; -namespace Dalamud.Plugin.Ipc.Internal +namespace Dalamud.Plugin.Ipc.Internal; + +/// +/// This class facilitates inter-plugin communication. +/// +[ServiceManager.EarlyLoadedService] +internal class CallGate : IServiceType { - /// - /// This class facilitates inter-plugin communication. - /// - [ServiceManager.EarlyLoadedService] - internal class CallGate : IServiceType + private readonly Dictionary gates = new(); + + [ServiceManager.ServiceConstructor] + private CallGate() { - private readonly Dictionary gates = new(); + } - [ServiceManager.ServiceConstructor] - private 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 c933b4cd1..2e2c7249e 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs @@ -7,188 +7,187 @@ 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; + var methodInfo = subscription.GetMethodInfo(); + this.CheckAndConvertArgs(args, methodInfo); + + subscription.DynamicInvoke(args); } + } - /// - /// Gets the name of the IPC registration. - /// - public string Name { get; init; } + /// + /// 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); - /// - /// Gets a list of delegate subscriptions for when SendMessage is called. - /// - public List Subscriptions { get; } = new(); + var methodInfo = this.Action.GetMethodInfo(); + this.CheckAndConvertArgs(args, methodInfo); - /// - /// Gets or sets an action for when InvokeAction is called. - /// - public Delegate Action { get; set; } + this.Action.DynamicInvoke(args); + } - /// - /// Gets or sets a func for when InvokeFunc is called. - /// - public Delegate Func { get; set; } + /// + /// 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); - /// - /// Invoke all actions that have subscribed to this IPC. - /// - /// Message arguments. - internal void SendMessage(object?[]? args) + 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.Subscriptions.Count == 0) - return; + var arg = args[i]; + var paramType = paramTypes[i]; - foreach (var subscription in this.Subscriptions) + if (arg == null) { - var methodInfo = subscription.GetMethodInfo(); - this.CheckAndConvertArgs(args, methodInfo); + if (paramType.IsValueType) + throw new IpcValueNullError(this.Name, paramType, i); - subscription.DynamicInvoke(args); + continue; } - } - /// - /// 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++) + var argType = arg.GetType(); + if (argType != paramType) { - var arg = args[i]; - var paramType = paramTypes[i]; - - if (arg == null) + // check the inheritance tree + var baseTypes = this.GenerateTypes(argType.BaseType); + if (baseTypes.Any(t => t == paramType)) { - if (paramType.IsValueType) - throw new IpcValueNullError(this.Name, paramType, i); - + // The source type inherits from the destination type continue; } - var argType = arg.GetType(); - if (argType != 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); - } - } - } - - 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 fb61453f3..4b2a981bd 100644 --- a/Dalamud/SafeMemory.cs +++ b/Dalamud/SafeMemory.cs @@ -2,292 +2,291 @@ 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")); + } + + /// + /// Marshals data from an unmanaged block of memory to a managed object. + /// + /// The type to create. + /// The address to read from. + /// The read object, or null, if it could not be read. + public static T? PtrToStructure(IntPtr addr) where T : struct => (T?)PtrToStructure(addr, typeof(T)); + + /// + /// Marshals data from an unmanaged block of memory to a managed object. + /// + /// The address to read from. + /// The type to create. + /// The read object, or null, if it could not be read. + public static object? PtrToStructure(IntPtr addr, Type type) + { + var size = Marshal.SizeOf(type); + + if (!ReadBytes(addr, size, out var buffer)) + return null; + + var mem = new LocalMemory(size); + mem.Write(buffer); + + return mem.Read(type); + } + + /// + /// 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 object? Read(Type type, int offset = 0) => Marshal.PtrToStructure(this.hGlobal + offset, type); + + 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")); - } - - /// - /// Marshals data from an unmanaged block of memory to a managed object. - /// - /// The type to create. - /// The address to read from. - /// The read object, or null, if it could not be read. - public static T? PtrToStructure(IntPtr addr) where T : struct => (T?)PtrToStructure(addr, typeof(T)); - - /// - /// Marshals data from an unmanaged block of memory to a managed object. - /// - /// The address to read from. - /// The type to create. - /// The read object, or null, if it could not be read. - public static object? PtrToStructure(IntPtr addr, Type type) - { - var size = Marshal.SizeOf(type); - - if (!ReadBytes(addr, size, out var buffer)) - return null; - - var mem = new LocalMemory(size); - mem.Write(buffer); - - return mem.Read(type); - } - - /// - /// 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 object? Read(Type type, int offset = 0) => Marshal.PtrToStructure(this.hGlobal + offset, type); - - 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/ServiceManager.cs b/Dalamud/ServiceManager.cs index 224cf6145..a7eb7457d 100644 --- a/Dalamud/ServiceManager.cs +++ b/Dalamud/ServiceManager.cs @@ -12,289 +12,288 @@ using Dalamud.Logging.Internal; using Dalamud.Utility.Timing; using JetBrains.Annotations; -namespace Dalamud +namespace Dalamud; + +/// +/// Class to initialize Service<T>s. +/// +internal static class ServiceManager { /// - /// Class to initialize Service<T>s. + /// Static log facility for Service{T}, to avoid duplicate instances for different types. /// - internal static class ServiceManager + public static readonly ModuleLog Log = new("SVC"); + + private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new(); + + private static readonly List LoadedServices = new(); + + /// + /// Gets task that gets completed when all blocking early loading services are done loading. + /// + public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task; + + /// + /// Initializes Provided Services and FFXIVClientStructs. + /// + /// Instance of . + /// Instance of . + /// Instance of . + public static void InitializeProvidedServicesAndClientStructs(Dalamud dalamud, DalamudStartInfo startInfo, DalamudConfiguration configuration) { - /// - /// Static log facility for Service{T}, to avoid duplicate instances for different types. - /// - public static readonly ModuleLog Log = new("SVC"); + // Initialize the process information. + var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs")); + if (!cacheDir.Exists) + cacheDir.Create(); - private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new(); - - private static readonly List LoadedServices = new(); - - /// - /// Gets task that gets completed when all blocking early loading services are done loading. - /// - public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task; - - /// - /// Initializes Provided Services and FFXIVClientStructs. - /// - /// Instance of . - /// Instance of . - /// Instance of . - public static void InitializeProvidedServicesAndClientStructs(Dalamud dalamud, DalamudStartInfo startInfo, DalamudConfiguration configuration) + lock (LoadedServices) { - // Initialize the process information. - var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs")); - if (!cacheDir.Exists) - cacheDir.Create(); + Service.Provide(dalamud); + LoadedServices.Add(typeof(Dalamud)); - lock (LoadedServices) - { - Service.Provide(dalamud); - LoadedServices.Add(typeof(Dalamud)); + Service.Provide(startInfo); + LoadedServices.Add(typeof(DalamudStartInfo)); - Service.Provide(startInfo); - LoadedServices.Add(typeof(DalamudStartInfo)); + Service.Provide(configuration); + LoadedServices.Add(typeof(DalamudConfiguration)); - Service.Provide(configuration); - LoadedServices.Add(typeof(DalamudConfiguration)); + Service.Provide(new ServiceContainer()); + LoadedServices.Add(typeof(ServiceContainer)); - Service.Provide(new ServiceContainer()); - LoadedServices.Add(typeof(ServiceContainer)); - - Service.Provide( - new SigScanner( - true, new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json")))); - LoadedServices.Add(typeof(SigScanner)); - } - - using (Timings.Start("CS Resolver Init")) - { - FFXIVClientStructs.Resolver.InitializeParallel( - new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json"))); - } + Service.Provide( + new SigScanner( + true, new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json")))); + LoadedServices.Add(typeof(SigScanner)); } - /// - /// Kicks off construction of services that can handle early loading. - /// - /// Task for initializing all services. - public static async Task InitializeEarlyLoadableServices() + using (Timings.Start("CS Resolver Init")) { - using var serviceInitializeTimings = Timings.Start("Services Init"); + FFXIVClientStructs.Resolver.InitializeParallel( + new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json"))); + } + } - var earlyLoadingServices = new HashSet(); - var blockingEarlyLoadingServices = new HashSet(); + /// + /// Kicks off construction of services that can handle early loading. + /// + /// Task for initializing all services. + public static async Task InitializeEarlyLoadableServices() + { + using var serviceInitializeTimings = Timings.Start("Services Init"); - var dependencyServicesMap = new Dictionary>(); - var getAsyncTaskMap = new Dictionary(); + var earlyLoadingServices = new HashSet(); + var blockingEarlyLoadingServices = new HashSet(); - foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes()) - { - var attr = serviceType.GetCustomAttribute(true)?.GetType(); - if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true) - continue; + var dependencyServicesMap = new Dictionary>(); + var getAsyncTaskMap = new Dictionary(); - var getTask = (Task)typeof(Service<>) - .MakeGenericType(serviceType) - .InvokeMember( - "GetAsync", - BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, - null, - null, - null); + foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes()) + { + var attr = serviceType.GetCustomAttribute(true)?.GetType(); + if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true) + continue; - if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService))) - { - getAsyncTaskMap[serviceType] = getTask; - blockingEarlyLoadingServices.Add(serviceType); - } - else - { - earlyLoadingServices.Add(serviceType); - } - - dependencyServicesMap[serviceType] = - (List)typeof(Service<>) + var getTask = (Task)typeof(Service<>) .MakeGenericType(serviceType) .InvokeMember( - "GetDependencyServices", + "GetAsync", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, null); + + if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService))) + { + getAsyncTaskMap[serviceType] = getTask; + blockingEarlyLoadingServices.Add(serviceType); + } + else + { + earlyLoadingServices.Add(serviceType); } - _ = Task.Run(async () => - { - try - { - await Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x])); - BlockingServicesLoadedTaskCompletionSource.SetResult(); - Timings.Event("BlockingServices Initialized"); - } - catch (Exception e) - { - BlockingServicesLoadedTaskCompletionSource.SetException(e); - } - }).ConfigureAwait(false); + dependencyServicesMap[serviceType] = + (List)typeof(Service<>) + .MakeGenericType(serviceType) + .InvokeMember( + "GetDependencyServices", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, + null, + null, + null); + } - var tasks = new List(); + _ = Task.Run(async () => + { try { - var servicesToLoad = new HashSet(); - servicesToLoad.UnionWith(earlyLoadingServices); - servicesToLoad.UnionWith(blockingEarlyLoadingServices); - - while (servicesToLoad.Any()) - { - foreach (var serviceType in servicesToLoad) - { - if (dependencyServicesMap[serviceType].Any( - x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false)) - continue; - - tasks.Add((Task)typeof(Service<>) - .MakeGenericType(serviceType) - .InvokeMember( - "StartLoader", - BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, - null, - null, - null)); - servicesToLoad.Remove(serviceType); - - tasks.Add(tasks.Last().ContinueWith(task => - { - if (task.IsFaulted) - return; - lock (LoadedServices) - { - LoadedServices.Add(serviceType); - } - })); - } - - if (!tasks.Any()) - throw new InvalidOperationException("Unresolvable dependency cycle detected"); - - if (servicesToLoad.Any()) - { - await Task.WhenAny(tasks); - var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray(); - if (faultedTasks.Any()) - throw new AggregateException(faultedTasks); - } - else - { - await Task.WhenAll(tasks); - } - - tasks.RemoveAll(x => x.IsCompleted); - } + await Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x])); + BlockingServicesLoadedTaskCompletionSource.SetResult(); + Timings.Event("BlockingServices Initialized"); } catch (Exception e) { - Log.Error(e, "Failed resolving services"); - try + BlockingServicesLoadedTaskCompletionSource.SetException(e); + } + }).ConfigureAwait(false); + + var tasks = new List(); + try + { + var servicesToLoad = new HashSet(); + servicesToLoad.UnionWith(earlyLoadingServices); + servicesToLoad.UnionWith(blockingEarlyLoadingServices); + + while (servicesToLoad.Any()) + { + foreach (var serviceType in servicesToLoad) { - BlockingServicesLoadedTaskCompletionSource.SetException(e); - } - catch (Exception) - { - // don't care, as this means task result/exception has already been set + if (dependencyServicesMap[serviceType].Any( + x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false)) + continue; + + tasks.Add((Task)typeof(Service<>) + .MakeGenericType(serviceType) + .InvokeMember( + "StartLoader", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, + null, + null, + null)); + servicesToLoad.Remove(serviceType); + + tasks.Add(tasks.Last().ContinueWith(task => + { + if (task.IsFaulted) + return; + lock (LoadedServices) + { + LoadedServices.Add(serviceType); + } + })); } - while (tasks.Any()) + if (!tasks.Any()) + throw new InvalidOperationException("Unresolvable dependency cycle detected"); + + if (servicesToLoad.Any()) { await Task.WhenAny(tasks); - tasks.RemoveAll(x => x.IsCompleted); + var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray(); + if (faultedTasks.Any()) + throw new AggregateException(faultedTasks); } - - UnloadAllServices(); - - throw; - } - } - - /// - /// Unloads all services, in the reverse order of load. - /// - public static void UnloadAllServices() - { - var framework = Service.GetNullable(Service.ExceptionPropagationMode.None); - if (framework is { IsInFrameworkUpdateThread: false, IsFrameworkUnloading: false }) - { - framework.RunOnFrameworkThread(UnloadAllServices).Wait(); - return; - } - - lock (LoadedServices) - { - while (LoadedServices.Any()) + else { - var serviceType = LoadedServices.Last(); - LoadedServices.RemoveAt(LoadedServices.Count - 1); - - typeof(Service<>) - .MakeGenericType(serviceType) - .InvokeMember( - "Unset", - BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, - null, - null, - null); + await Task.WhenAll(tasks); } + + tasks.RemoveAll(x => x.IsCompleted); } } - - /// - /// Indicates that this constructor will be called for early initialization. - /// - [AttributeUsage(AttributeTargets.Constructor)] - [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public class ServiceConstructor : Attribute + catch (Exception e) { - } + Log.Error(e, "Failed resolving services"); + try + { + BlockingServicesLoadedTaskCompletionSource.SetException(e); + } + catch (Exception) + { + // don't care, as this means task result/exception has already been set + } - /// - /// Indicates that the field is a service that should be loaded before constructing the class. - /// - [AttributeUsage(AttributeTargets.Field)] - public class ServiceDependency : Attribute - { - } + while (tasks.Any()) + { + await Task.WhenAny(tasks); + tasks.RemoveAll(x => x.IsCompleted); + } - /// - /// Indicates that the class is a service. - /// - [AttributeUsage(AttributeTargets.Class)] - public class Service : Attribute - { - } + UnloadAllServices(); - /// - /// Indicates that the class is a service, and will be instantiated automatically on startup. - /// - [AttributeUsage(AttributeTargets.Class)] - public class EarlyLoadedService : Service - { - } - - /// - /// Indicates that the class is a service, and will be instantiated automatically on startup, - /// blocking game main thread until it completes. - /// - [AttributeUsage(AttributeTargets.Class)] - public class BlockingEarlyLoadedService : EarlyLoadedService - { - } - - /// - /// Indicates that the method should be called when the services given in the constructor are ready. - /// - [AttributeUsage(AttributeTargets.Method)] - [MeansImplicitUse] - public class CallWhenServicesReady : Attribute - { + throw; } } + + /// + /// Unloads all services, in the reverse order of load. + /// + public static void UnloadAllServices() + { + var framework = Service.GetNullable(Service.ExceptionPropagationMode.None); + if (framework is { IsInFrameworkUpdateThread: false, IsFrameworkUnloading: false }) + { + framework.RunOnFrameworkThread(UnloadAllServices).Wait(); + return; + } + + lock (LoadedServices) + { + while (LoadedServices.Any()) + { + var serviceType = LoadedServices.Last(); + LoadedServices.RemoveAt(LoadedServices.Count - 1); + + typeof(Service<>) + .MakeGenericType(serviceType) + .InvokeMember( + "Unset", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, + null, + null, + null); + } + } + } + + /// + /// Indicates that this constructor will be called for early initialization. + /// + [AttributeUsage(AttributeTargets.Constructor)] + [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public class ServiceConstructor : Attribute + { + } + + /// + /// Indicates that the field is a service that should be loaded before constructing the class. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ServiceDependency : Attribute + { + } + + /// + /// Indicates that the class is a service. + /// + [AttributeUsage(AttributeTargets.Class)] + public class Service : Attribute + { + } + + /// + /// Indicates that the class is a service, and will be instantiated automatically on startup. + /// + [AttributeUsage(AttributeTargets.Class)] + public class EarlyLoadedService : Service + { + } + + /// + /// Indicates that the class is a service, and will be instantiated automatically on startup, + /// blocking game main thread until it completes. + /// + [AttributeUsage(AttributeTargets.Class)] + public class BlockingEarlyLoadedService : EarlyLoadedService + { + } + + /// + /// Indicates that the method should be called when the services given in the constructor are ready. + /// + [AttributeUsage(AttributeTargets.Method)] + [MeansImplicitUse] + public class CallWhenServicesReady : Attribute + { + } } diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs index aa014878d..5b8085ac8 100644 --- a/Dalamud/Service{T}.cs +++ b/Dalamud/Service{T}.cs @@ -9,253 +9,252 @@ using Dalamud.IoC.Internal; using Dalamud.Utility.Timing; using JetBrains.Annotations; -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 : IServiceType { - /// - /// 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 : IServiceType + private static TaskCompletionSource instanceTcs = new(); + + static Service() { - private static TaskCompletionSource instanceTcs = new(); + var exposeToPlugins = typeof(T).GetCustomAttribute() != null; + if (exposeToPlugins) + ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name); + else + ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name); - static Service() - { - var exposeToPlugins = typeof(T).GetCustomAttribute() != null; - if (exposeToPlugins) - ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name); - else - ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name); + if (exposeToPlugins) + Service.Get().RegisterSingleton(instanceTcs.Task); + } - if (exposeToPlugins) - Service.Get().RegisterSingleton(instanceTcs.Task); - } + /// + /// Specifies how to handle the cases of failed services when calling . + /// + public enum ExceptionPropagationMode + { + /// + /// Propagate all exceptions. + /// + PropagateAll, /// - /// Specifies how to handle the cases of failed services when calling . + /// Propagate all exceptions, except for . /// - public enum ExceptionPropagationMode - { - /// - /// Propagate all exceptions. - /// - PropagateAll, - - /// - /// Propagate all exceptions, except for . - /// - PropagateNonUnloaded, - - /// - /// Treat all exceptions as null. - /// - None, - } + PropagateNonUnloaded, /// - /// Sets the type in the service locator to the given object. + /// Treat all exceptions as null. /// - /// Object to set. - public static void Provide(T obj) - { - instanceTcs.SetResult(obj); - ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); - } + None, + } - /// - /// Sets the service load state to failure. - /// - /// The exception. - public static void ProvideException(Exception exception) - { - ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name); - instanceTcs.SetException(exception); - } + /// + /// Sets the type in the service locator to the given object. + /// + /// Object to set. + public static void Provide(T obj) + { + instanceTcs.SetResult(obj); + ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); + } - /// - /// Pull the instance out of the service locator, waiting if necessary. - /// - /// The object. - public static T Get() - { - if (!instanceTcs.Task.IsCompleted) - instanceTcs.Task.Wait(); + /// + /// Sets the service load state to failure. + /// + /// The exception. + public static void ProvideException(Exception exception) + { + ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name); + instanceTcs.SetException(exception); + } + + /// + /// Pull the instance out of the service locator, waiting if necessary. + /// + /// The object. + public static T Get() + { + if (!instanceTcs.Task.IsCompleted) + instanceTcs.Task.Wait(); + return instanceTcs.Task.Result; + } + + /// + /// Pull the instance out of the service locator, waiting if necessary. + /// + /// The object. + [UsedImplicitly] + public static Task GetAsync() => instanceTcs.Task; + + /// + /// Attempt to pull the instance out of the service locator. + /// + /// Specifies which exceptions to propagate. + /// The object if registered, null otherwise. + public static T? GetNullable(ExceptionPropagationMode propagateException = ExceptionPropagationMode.PropagateNonUnloaded) + { + if (instanceTcs.Task.IsCompletedSuccessfully) return instanceTcs.Task.Result; + if (instanceTcs.Task.IsFaulted && propagateException != ExceptionPropagationMode.None) + { + if (propagateException == ExceptionPropagationMode.PropagateNonUnloaded + && instanceTcs.Task.Exception!.InnerExceptions.FirstOrDefault() is UnloadedException) + return default; + throw instanceTcs.Task.Exception!; } - /// - /// Pull the instance out of the service locator, waiting if necessary. - /// - /// The object. - [UsedImplicitly] - public static Task GetAsync() => instanceTcs.Task; + return default; + } - /// - /// Attempt to pull the instance out of the service locator. - /// - /// Specifies which exceptions to propagate. - /// The object if registered, null otherwise. - public static T? GetNullable(ExceptionPropagationMode propagateException = ExceptionPropagationMode.PropagateNonUnloaded) + /// + /// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking. + /// + /// List of dependency services. + [UsedImplicitly] + public static List GetDependencyServices() + { + var res = new List(); + res.AddRange(GetServiceConstructor() + .GetParameters() + .Select(x => x.ParameterType)); + res.AddRange(typeof(T) + .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Select(x => x.FieldType) + .Where(x => x.GetCustomAttribute(true) != null)); + return res + .Distinct() + .Select(x => typeof(Service<>).MakeGenericType(x)) + .ToList(); + } + + [UsedImplicitly] + private static Task StartLoader() + { + if (instanceTcs.Task.IsCompleted) + throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed."); + + var attr = typeof(T).GetCustomAttribute(true)?.GetType(); + if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true) + throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService"); + + return Task.Run(Timings.AttachTimingHandle(async () => { - if (instanceTcs.Task.IsCompletedSuccessfully) - return instanceTcs.Task.Result; - if (instanceTcs.Task.IsFaulted && propagateException != ExceptionPropagationMode.None) + ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name); + try { - if (propagateException == ExceptionPropagationMode.PropagateNonUnloaded - && instanceTcs.Task.Exception!.InnerExceptions.FirstOrDefault() is UnloadedException) - return default; - throw instanceTcs.Task.Exception!; - } + var instance = await ConstructObject(); + instanceTcs.SetResult(instance); - return default; - } - - /// - /// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking. - /// - /// List of dependency services. - [UsedImplicitly] - public static List GetDependencyServices() - { - var res = new List(); - res.AddRange(GetServiceConstructor() - .GetParameters() - .Select(x => x.ParameterType)); - res.AddRange(typeof(T) - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Select(x => x.FieldType) - .Where(x => x.GetCustomAttribute(true) != null)); - return res - .Distinct() - .Select(x => typeof(Service<>).MakeGenericType(x)) - .ToList(); - } - - [UsedImplicitly] - private static Task StartLoader() - { - if (instanceTcs.Task.IsCompleted) - throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed."); - - var attr = typeof(T).GetCustomAttribute(true)?.GetType(); - if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true) - throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService"); - - return Task.Run(Timings.AttachTimingHandle(async () => - { - ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name); - try + foreach (var method in typeof(T).GetMethods( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - var instance = await ConstructObject(); - instanceTcs.SetResult(instance); + if (method.GetCustomAttribute(true) == null) + continue; - foreach (var method in typeof(T).GetMethods( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - { - if (method.GetCustomAttribute(true) == null) - continue; - - ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name); - var args = await Task.WhenAll(method.GetParameters().Select( - x => ResolveServiceFromTypeAsync(x.ParameterType))); - method.Invoke(instance, args); - } - - ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name); - return instance; + ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name); + var args = await Task.WhenAll(method.GetParameters().Select( + x => ResolveServiceFromTypeAsync(x.ParameterType))); + method.Invoke(instance, args); } - catch (Exception e) - { - ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name); - instanceTcs.SetException(e); - throw; - } - })); - } - [UsedImplicitly] - private static void Unset() - { - if (!instanceTcs.Task.IsCompletedSuccessfully) - return; - - var instance = instanceTcs.Task.Result; - if (instance is IDisposable disposable) - { - ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name); - try - { - disposable.Dispose(); - ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name); - } - catch (Exception e) - { - ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name); - } + ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name); + return instance; } - else + catch (Exception e) { - ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name); + ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name); + instanceTcs.SetException(e); + throw; } + })); + } - instanceTcs = new TaskCompletionSource(); - instanceTcs.SetException(new UnloadedException()); - } + [UsedImplicitly] + private static void Unset() + { + if (!instanceTcs.Task.IsCompletedSuccessfully) + return; - private static async Task ResolveServiceFromTypeAsync(Type type) + var instance = instanceTcs.Task.Result; + if (instance is IDisposable disposable) { - var task = (Task)typeof(Service<>) - .MakeGenericType(type) - .InvokeMember( - "GetAsync", - BindingFlags.InvokeMethod | - BindingFlags.Static | - BindingFlags.Public, - null, - null, - null)!; - await task; - return typeof(Task<>).MakeGenericType(type) - .GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)! - .GetValue(task); - } - - private static ConstructorInfo GetServiceConstructor() - { - const BindingFlags ctorBindingFlags = - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; - return typeof(T) - .GetConstructors(ctorBindingFlags) - .Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any()); - } - - private static async Task ConstructObject() - { - var ctor = GetServiceConstructor(); - var args = await Task.WhenAll( - ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType))); - using (Timings.Start($"{typeof(T).Name} Construct")) + ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name); + try { - return (T)ctor.Invoke(args)!; + disposable.Dispose(); + ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name); + } + catch (Exception e) + { + ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name); } } + else + { + ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name); + } + instanceTcs = new TaskCompletionSource(); + instanceTcs.SetException(new UnloadedException()); + } + + private static async Task ResolveServiceFromTypeAsync(Type type) + { + var task = (Task)typeof(Service<>) + .MakeGenericType(type) + .InvokeMember( + "GetAsync", + BindingFlags.InvokeMethod | + BindingFlags.Static | + BindingFlags.Public, + null, + null, + null)!; + await task; + return typeof(Task<>).MakeGenericType(type) + .GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)! + .GetValue(task); + } + + private static ConstructorInfo GetServiceConstructor() + { + const BindingFlags ctorBindingFlags = + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; + return typeof(T) + .GetConstructors(ctorBindingFlags) + .Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any()); + } + + private static async Task ConstructObject() + { + var ctor = GetServiceConstructor(); + var args = await Task.WhenAll( + ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType))); + using (Timings.Start($"{typeof(T).Name} Construct")) + { + return (T)ctor.Invoke(args)!; + } + } + + /// + /// Exception thrown when service is attempted to be retrieved when it's unloaded. + /// + public class UnloadedException : InvalidOperationException + { /// - /// Exception thrown when service is attempted to be retrieved when it's unloaded. + /// Initializes a new instance of the class. /// - public class UnloadedException : InvalidOperationException + public UnloadedException() + : base("Service is unloaded.") { - /// - /// Initializes a new instance of the class. - /// - public UnloadedException() - : base("Service is unloaded.") - { - } } } } diff --git a/Dalamud/Support/BugBait.cs b/Dalamud/Support/BugBait.cs index 2de4ee7e5..084c49d9d 100644 --- a/Dalamud/Support/BugBait.cs +++ b/Dalamud/Support/BugBait.cs @@ -6,68 +6,67 @@ 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. + /// Whether or not the plugin is a testing plugin. + /// 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, bool isTesting, 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. - /// Whether or not the plugin is a testing plugin. - /// 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, bool isTesting, string content, string reporter, bool includeException) + var model = new FeedbackModel { - if (content.IsNullOrWhitespace()) - return; + Content = content, + Reporter = reporter, + Name = plugin.InternalName, + Version = isTesting ? plugin.TestingAssemblyVersion?.ToString() : plugin.AssemblyVersion.ToString(), + DalamudHash = Util.GetGitHash(), + }; - var model = new FeedbackModel - { - Content = content, - Reporter = reporter, - Name = plugin.InternalName, - Version = isTesting ? plugin.TestingAssemblyVersion?.ToString() : 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 17f15de94..922fc5e50 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -12,119 +12,118 @@ 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) + var fixedContext = context?.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + + try { - LastException = exception; - - var fixedContext = context?.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - - try + var payload = new ExceptionPayload { - var payload = new ExceptionPayload - { - Context = fixedContext, - When = DateTime.Now, - Info = exception.ToString(), - }; + Context = fixedContext, + 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)?.OrderByDescending(x => x.InternalName).ToArray(), - DalamudVersion = Util.AssemblyVersion, - DalamudGitHash = Util.GetGitHash(), - GameVersion = startInfo.GameVersion.ToString(), - Language = startInfo.Language.ToString(), - BetaKey = configuration.DalamudBetaKey, - DoPluginTest = configuration.DoPluginTest, - LoadAllApiLevels = pluginManager?.LoadAllApiLevels == true, - InterfaceLoaded = interfaceManager?.IsReady ?? false, - HasThirdRepo = configuration.ThirdRepoList is { Count: > 0 }, - 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 LocalPluginManifest[] 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 => false; - - public string? BetaKey { get; set; } - - public bool DoPluginTest { get; set; } - - public bool LoadAllApiLevels { get; set; } - - public bool InterfaceLoaded { get; set; } - - public bool ForcedMinHook { get; set; } - - public List ThirdRepo => new(); - - public bool HasThirdRepo { 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)?.OrderByDescending(x => x.InternalName).ToArray(), + DalamudVersion = Util.AssemblyVersion, + DalamudGitHash = Util.GetGitHash(), + GameVersion = startInfo.GameVersion.ToString(), + Language = startInfo.Language.ToString(), + BetaKey = configuration.DalamudBetaKey, + DoPluginTest = configuration.DoPluginTest, + LoadAllApiLevels = pluginManager?.LoadAllApiLevels == true, + InterfaceLoaded = interfaceManager?.IsReady ?? false, + HasThirdRepo = configuration.ThirdRepoList is { Count: > 0 }, + 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 LocalPluginManifest[] 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 => false; + + public string? BetaKey { get; set; } + + public bool DoPluginTest { get; set; } + + public bool LoadAllApiLevels { get; set; } + + public bool InterfaceLoaded { get; set; } + + public bool ForcedMinHook { get; set; } + + public List ThirdRepo => new(); + + public bool HasThirdRepo { 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/EventHandlerExtensions.cs b/Dalamud/Utility/EventHandlerExtensions.cs index f07b46d10..bce815a7b 100644 --- a/Dalamud/Utility/EventHandlerExtensions.cs +++ b/Dalamud/Utility/EventHandlerExtensions.cs @@ -6,93 +6,92 @@ using Serilog; using static Dalamud.Game.Framework; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Extensions for Events. +/// +internal static class EventHandlerExtensions { /// - /// Extensions for Events. + /// Replacement for Invoke() on EventHandlers to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. /// - internal static class EventHandlerExtensions + /// The EventHandler in question. + /// Default sender for Invoke equivalent. + /// Default EventArgs for Invoke equivalent. + public static void InvokeSafely(this EventHandler eh, object sender, EventArgs e) { - /// - /// Replacement for Invoke() on EventHandlers to catch exceptions that stop event propagation in case - /// of a thrown Exception inside of an invocation. - /// - /// The EventHandler in question. - /// Default sender for Invoke equivalent. - /// Default EventArgs for Invoke equivalent. - public static void InvokeSafely(this EventHandler eh, object sender, EventArgs e) - { - if (eh == null) - return; + if (eh == null) + return; - foreach (var handler in eh.GetInvocationList().Cast()) - { - HandleInvoke(() => handler(sender, e)); - } + foreach (var handler in eh.GetInvocationList().Cast()) + { + HandleInvoke(() => handler(sender, e)); } + } - /// - /// Replacement for Invoke() on generic EventHandlers to catch exceptions that stop event propagation in case - /// of a thrown Exception inside of an invocation. - /// - /// The EventHandler in question. - /// Default sender for Invoke equivalent. - /// Default EventArgs for Invoke equivalent. - /// Type of EventArgs. - public static void InvokeSafely(this EventHandler eh, object sender, T e) + /// + /// Replacement for Invoke() on generic EventHandlers to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The EventHandler in question. + /// Default sender for Invoke equivalent. + /// Default EventArgs for Invoke equivalent. + /// Type of EventArgs. + public static void InvokeSafely(this EventHandler eh, object sender, T e) + { + if (eh == null) + return; + + foreach (var handler in eh.GetInvocationList().Cast>()) { - if (eh == null) - return; - - foreach (var handler in eh.GetInvocationList().Cast>()) - { - HandleInvoke(() => handler(sender, e)); - } + HandleInvoke(() => handler(sender, e)); } + } - /// - /// Replacement for Invoke() on event Actions to catch exceptions that stop event propagation in case - /// of a thrown Exception inside of an invocation. - /// - /// The Action in question. - public static void InvokeSafely(this Action act) + /// + /// Replacement for Invoke() on event Actions to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The Action in question. + public static void InvokeSafely(this Action act) + { + if (act == null) + return; + + foreach (var action in act.GetInvocationList().Cast()) { - if (act == null) - return; - - foreach (var action in act.GetInvocationList().Cast()) - { - HandleInvoke(action); - } + HandleInvoke(action); } + } - /// - /// Replacement for Invoke() on OnUpdateDelegate to catch exceptions that stop event propagation in case - /// of a thrown Exception inside of an invocation. - /// - /// The OnUpdateDelegate in question. - /// Framework to be passed on to OnUpdateDelegate. - public static void InvokeSafely(this OnUpdateDelegate updateDelegate, Framework framework) + /// + /// Replacement for Invoke() on OnUpdateDelegate to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The OnUpdateDelegate in question. + /// Framework to be passed on to OnUpdateDelegate. + public static void InvokeSafely(this OnUpdateDelegate updateDelegate, Framework framework) + { + if (updateDelegate == null) + return; + + foreach (var action in updateDelegate.GetInvocationList().Cast()) { - if (updateDelegate == null) - return; - - foreach (var action in updateDelegate.GetInvocationList().Cast()) - { - HandleInvoke(() => action(framework)); - } + HandleInvoke(() => action(framework)); } + } - private static void HandleInvoke(Action act) + private static void HandleInvoke(Action act) + { + try { - try - { - act(); - } - catch (Exception ex) - { - Log.Error(ex, "Exception during raise of {handler}", act.Method); - } + act(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", act.Method); } } } diff --git a/Dalamud/Utility/Hash.cs b/Dalamud/Utility/Hash.cs index 2fe5fd1d6..a8296315e 100644 --- a/Dalamud/Utility/Hash.cs +++ b/Dalamud/Utility/Hash.cs @@ -1,35 +1,34 @@ using System; using System.Security.Cryptography; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Utility functions for hashing. +/// +public static class Hash { /// - /// Utility functions for hashing. + /// Get the SHA-256 hash of a string. /// - public static class Hash + /// The string to hash. + /// The computed hash. + internal static string GetStringSha256Hash(string text) { - /// - /// Get the SHA-256 hash of a string. - /// - /// The string to hash. - /// The computed hash. - internal static string GetStringSha256Hash(string text) - { - return string.IsNullOrEmpty(text) ? string.Empty : GetSha256Hash(System.Text.Encoding.UTF8.GetBytes(text)); - } - - /// - /// Get the SHA-256 hash of a byte array. - /// - /// The byte array to hash. - /// The computed hash. - internal static string GetSha256Hash(byte[] buffer) - { - using var sha = SHA256.Create(); - var hash = sha.ComputeHash(buffer); - return ByteArrayToString(hash); - } - - private static string ByteArrayToString(byte[] ba) => BitConverter.ToString(ba).Replace("-", string.Empty); + return string.IsNullOrEmpty(text) ? string.Empty : GetSha256Hash(System.Text.Encoding.UTF8.GetBytes(text)); } + + /// + /// Get the SHA-256 hash of a byte array. + /// + /// The byte array to hash. + /// The computed hash. + internal static string GetSha256Hash(byte[] buffer) + { + using var sha = SHA256.Create(); + var hash = sha.ComputeHash(buffer); + return ByteArrayToString(hash); + } + + private static string ByteArrayToString(byte[] ba) => BitConverter.ToString(ba).Replace("-", string.Empty); } diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index dca423eab..4833bd629 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,18 +1,17 @@ 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/Signatures/Fallibility.cs b/Dalamud/Utility/Signatures/Fallibility.cs index 1e5c502cf..e0c1a7a6a 100755 --- a/Dalamud/Utility/Signatures/Fallibility.cs +++ b/Dalamud/Utility/Signatures/Fallibility.cs @@ -1,24 +1,23 @@ -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// The fallibility of a signature. +/// +public enum Fallibility { /// - /// The fallibility of a signature. + /// The fallibility of the signature is determined by the field/property's + /// nullability. /// - public enum Fallibility - { - /// - /// The fallibility of the signature is determined by the field/property's - /// nullability. - /// - Auto, + Auto, - /// - /// The signature is fallible. - /// - Fallible, + /// + /// The signature is fallible. + /// + Fallible, - /// - /// The signature is infallible. - /// - Infallible, - } + /// + /// The signature is infallible. + /// + Infallible, } diff --git a/Dalamud/Utility/Signatures/NullabilityUtil.cs b/Dalamud/Utility/Signatures/NullabilityUtil.cs index 0a2fb7fc6..9db1117cc 100755 --- a/Dalamud/Utility/Signatures/NullabilityUtil.cs +++ b/Dalamud/Utility/Signatures/NullabilityUtil.cs @@ -4,74 +4,73 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reflection; -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// Class providing info about field, property or parameter nullability. +/// +internal static class NullabilityUtil { /// - /// Class providing info about field, property or parameter nullability. + /// Check if the provided property is nullable. /// - internal static class NullabilityUtil + /// The property to check. + /// Whether or not the property is nullable. + internal static bool IsNullable(PropertyInfo property) => IsNullableHelper(property.PropertyType, property.DeclaringType, property.CustomAttributes); + + /// + /// Check if the provided field is nullable. + /// + /// The field to check. + /// Whether or not the field is nullable. + internal static bool IsNullable(FieldInfo field) => IsNullableHelper(field.FieldType, field.DeclaringType, field.CustomAttributes); + + /// + /// Check if the provided parameter is nullable. + /// + /// The parameter to check. + /// Whether or not the parameter is nullable. + internal static bool IsNullable(ParameterInfo parameter) => IsNullableHelper(parameter.ParameterType, parameter.Member, parameter.CustomAttributes); + + private static bool IsNullableHelper(Type memberType, MemberInfo? declaringType, IEnumerable customAttributes) { - /// - /// Check if the provided property is nullable. - /// - /// The property to check. - /// Whether or not the property is nullable. - internal static bool IsNullable(PropertyInfo property) => IsNullableHelper(property.PropertyType, property.DeclaringType, property.CustomAttributes); - - /// - /// Check if the provided field is nullable. - /// - /// The field to check. - /// Whether or not the field is nullable. - internal static bool IsNullable(FieldInfo field) => IsNullableHelper(field.FieldType, field.DeclaringType, field.CustomAttributes); - - /// - /// Check if the provided parameter is nullable. - /// - /// The parameter to check. - /// Whether or not the parameter is nullable. - internal static bool IsNullable(ParameterInfo parameter) => IsNullableHelper(parameter.ParameterType, parameter.Member, parameter.CustomAttributes); - - private static bool IsNullableHelper(Type memberType, MemberInfo? declaringType, IEnumerable customAttributes) + if (memberType.IsValueType) { - if (memberType.IsValueType) - { - return Nullable.GetUnderlyingType(memberType) != null; - } - - var nullable = customAttributes - .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); - if (nullable != null && nullable.ConstructorArguments.Count == 1) - { - var attributeArgument = nullable.ConstructorArguments[0]; - if (attributeArgument.ArgumentType == typeof(byte[])) - { - var args = (ReadOnlyCollection)attributeArgument.Value!; - if (args.Count > 0 && args[0].ArgumentType == typeof(byte)) - { - return (byte)args[0].Value! == 2; - } - } - else if (attributeArgument.ArgumentType == typeof(byte)) - { - return (byte)attributeArgument.Value! == 2; - } - } - - for (var type = declaringType; type != null; type = type.DeclaringType) - { - var context = type.CustomAttributes - .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); - if (context != null && - context.ConstructorArguments.Count == 1 && - context.ConstructorArguments[0].ArgumentType == typeof(byte)) - { - return (byte)context.ConstructorArguments[0].Value! == 2; - } - } - - // Couldn't find a suitable attribute - return false; + return Nullable.GetUnderlyingType(memberType) != null; } + + var nullable = customAttributes + .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); + if (nullable != null && nullable.ConstructorArguments.Count == 1) + { + var attributeArgument = nullable.ConstructorArguments[0]; + if (attributeArgument.ArgumentType == typeof(byte[])) + { + var args = (ReadOnlyCollection)attributeArgument.Value!; + if (args.Count > 0 && args[0].ArgumentType == typeof(byte)) + { + return (byte)args[0].Value! == 2; + } + } + else if (attributeArgument.ArgumentType == typeof(byte)) + { + return (byte)attributeArgument.Value! == 2; + } + } + + for (var type = declaringType; type != null; type = type.DeclaringType) + { + var context = type.CustomAttributes + .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); + if (context != null && + context.ConstructorArguments.Count == 1 && + context.ConstructorArguments[0].ArgumentType == typeof(byte)) + { + return (byte)context.ConstructorArguments[0].Value! == 2; + } + } + + // Couldn't find a suitable attribute + return false; } } diff --git a/Dalamud/Utility/Signatures/ScanType.cs b/Dalamud/Utility/Signatures/ScanType.cs index 58e861805..ef06ff0c5 100755 --- a/Dalamud/Utility/Signatures/ScanType.cs +++ b/Dalamud/Utility/Signatures/ScanType.cs @@ -1,22 +1,21 @@ using Dalamud.Game; -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// The type of scan to perform with a signature. +/// +public enum ScanType { /// - /// The type of scan to perform with a signature. + /// Scan the text section of the executable. Uses + /// . /// - public enum ScanType - { - /// - /// Scan the text section of the executable. Uses - /// . - /// - Text, + Text, - /// - /// Scans the text section of the executable in order to find a data section - /// address. Uses . - /// - StaticAddress, - } + /// + /// Scans the text section of the executable in order to find a data section + /// address. Uses . + /// + StaticAddress, } diff --git a/Dalamud/Utility/Signatures/SignatureAttribute.cs b/Dalamud/Utility/Signatures/SignatureAttribute.cs index 49fe8eab0..e4831fd8d 100755 --- a/Dalamud/Utility/Signatures/SignatureAttribute.cs +++ b/Dalamud/Utility/Signatures/SignatureAttribute.cs @@ -2,69 +2,68 @@ using JetBrains.Annotations; -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// The main way to use SignatureHelper. Apply this attribute to any field/property +/// that should make use of a signature. See the field documentation for more +/// information. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +[MeansImplicitUse(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Itself)] +// ReSharper disable once ClassNeverInstantiated.Global +public sealed class SignatureAttribute : Attribute { /// - /// The main way to use SignatureHelper. Apply this attribute to any field/property - /// that should make use of a signature. See the field documentation for more - /// information. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - [MeansImplicitUse(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Itself)] - // ReSharper disable once ClassNeverInstantiated.Global - public sealed class SignatureAttribute : Attribute + /// signature to scan for, see . + public SignatureAttribute(string signature) { - /// - /// Initializes a new instance of the class. - /// - /// signature to scan for, see . - public SignatureAttribute(string signature) - { - this.Signature = signature; - } - - /// - /// Gets the memory signature for this field/property. - /// - public string Signature { get; init; } - - /// - /// Gets the way this signature should be used. By default, this is guessed using - /// simple heuristics, but it can be manually specified if SignatureHelper can't - /// figure it out. - /// - /// - /// - public SignatureUseFlags UseFlags { get; init; } = SignatureUseFlags.Auto; - - /// - /// Gets the type of scan to perform. By default, this scans the text section of - /// the executable, but this should be set to StaticAddress for static - /// addresses. - /// - public ScanType ScanType { get; init; } = ScanType.Text; - - /// - /// Gets the detour name if this signature is for a hook. SignatureHelper will search - /// the type containing this field/property for a method that matches the - /// hook's delegate type, but if it doesn't find one or finds more than one, - /// it will fail. You can specify the name of the method here to avoid this. - /// - public string? DetourName { get; init; } - - /// - /// Gets the offset from the signature to read memory from, when is set to Offset. - /// - public int Offset { get; init; } - - /// - /// Gets the fallibility of the signature. - /// When a signature is fallible, any errors while resolving it will be - /// logged in the Dalamud log and the field/property will not have its value - /// set. When a signature is not fallible, any errors will be thrown as - /// exceptions instead. If fallibility is not specified, it is inferred - /// based on if the field/property is nullable. - /// - public Fallibility Fallibility { get; init; } = Fallibility.Auto; + this.Signature = signature; } + + /// + /// Gets the memory signature for this field/property. + /// + public string Signature { get; init; } + + /// + /// Gets the way this signature should be used. By default, this is guessed using + /// simple heuristics, but it can be manually specified if SignatureHelper can't + /// figure it out. + /// + /// + /// + public SignatureUseFlags UseFlags { get; init; } = SignatureUseFlags.Auto; + + /// + /// Gets the type of scan to perform. By default, this scans the text section of + /// the executable, but this should be set to StaticAddress for static + /// addresses. + /// + public ScanType ScanType { get; init; } = ScanType.Text; + + /// + /// Gets the detour name if this signature is for a hook. SignatureHelper will search + /// the type containing this field/property for a method that matches the + /// hook's delegate type, but if it doesn't find one or finds more than one, + /// it will fail. You can specify the name of the method here to avoid this. + /// + public string? DetourName { get; init; } + + /// + /// Gets the offset from the signature to read memory from, when is set to Offset. + /// + public int Offset { get; init; } + + /// + /// Gets the fallibility of the signature. + /// When a signature is fallible, any errors while resolving it will be + /// logged in the Dalamud log and the field/property will not have its value + /// set. When a signature is not fallible, any errors will be thrown as + /// exceptions instead. If fallibility is not specified, it is inferred + /// based on if the field/property is nullable. + /// + public Fallibility Fallibility { get; init; } = Fallibility.Auto; } diff --git a/Dalamud/Utility/Signatures/SignatureException.cs b/Dalamud/Utility/Signatures/SignatureException.cs index 6c3efd6dd..ea7cef396 100755 --- a/Dalamud/Utility/Signatures/SignatureException.cs +++ b/Dalamud/Utility/Signatures/SignatureException.cs @@ -1,19 +1,18 @@ using System; -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// An exception for signatures. +/// +public class SignatureException : Exception { /// - /// An exception for signatures. + /// Initializes a new instance of the class. /// - public class SignatureException : Exception + /// Message. + internal SignatureException(string message) + : base(message) { - /// - /// Initializes a new instance of the class. - /// - /// Message. - internal SignatureException(string message) - : base(message) - { - } } } diff --git a/Dalamud/Utility/Signatures/SignatureHelper.cs b/Dalamud/Utility/Signatures/SignatureHelper.cs index 192ee56ee..b58cec9d8 100755 --- a/Dalamud/Utility/Signatures/SignatureHelper.cs +++ b/Dalamud/Utility/Signatures/SignatureHelper.cs @@ -8,177 +8,176 @@ using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures.Wrappers; -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// A utility class to help reduce signature boilerplate code. +/// +public static class SignatureHelper { + private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + /// - /// A utility class to help reduce signature boilerplate code. + /// Initialises an object's fields and properties that are annotated with a + /// . /// - public static class SignatureHelper + /// The object to initialise. + /// If warnings should be logged using . + public static void Initialise(object self, bool log = true) { - private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + var scanner = Service.Get(); + var selfType = self.GetType(); + var fields = selfType.GetFields(Flags).Select(field => (IFieldOrPropertyInfo)new FieldInfoWrapper(field)) + .Concat(selfType.GetProperties(Flags).Select(prop => new PropertyInfoWrapper(prop))) + .Select(field => (field, field.GetCustomAttribute())) + .Where(field => field.Item2 != null); - /// - /// Initialises an object's fields and properties that are annotated with a - /// . - /// - /// The object to initialise. - /// If warnings should be logged using . - public static void Initialise(object self, bool log = true) + foreach (var (info, sig) in fields) { - var scanner = Service.Get(); - var selfType = self.GetType(); - var fields = selfType.GetFields(Flags).Select(field => (IFieldOrPropertyInfo)new FieldInfoWrapper(field)) - .Concat(selfType.GetProperties(Flags).Select(prop => new PropertyInfoWrapper(prop))) - .Select(field => (field, field.GetCustomAttribute())) - .Where(field => field.Item2 != null); - - foreach (var (info, sig) in fields) + var wasWrapped = false; + var actualType = info.ActualType; + if (actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - var wasWrapped = false; - var actualType = info.ActualType; - if (actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Nullable<>)) + // unwrap the nullable + actualType = actualType.GetGenericArguments()[0]; + wasWrapped = true; + } + + var fallibility = sig!.Fallibility; + if (fallibility == Fallibility.Auto) + { + fallibility = info.IsNullable || wasWrapped + ? Fallibility.Fallible + : Fallibility.Infallible; + } + + var fallible = fallibility == Fallibility.Fallible; + + void Invalid(string message, bool prepend = true) + { + var errorMsg = prepend + ? $"Invalid Signature attribute for {selfType.FullName}.{info.Name}: {message}" + : message; + if (fallible) { - // unwrap the nullable - actualType = actualType.GetGenericArguments()[0]; - wasWrapped = true; + PluginLog.Warning(errorMsg); + } + else + { + throw new SignatureException(errorMsg); + } + } + + IntPtr ptr; + var success = sig.ScanType == ScanType.Text + ? scanner.TryScanText(sig.Signature, out ptr) + : scanner.TryGetStaticAddressFromSig(sig.Signature, out ptr); + if (!success) + { + if (log) + { + Invalid($"Failed to find {sig.ScanType} signature \"{info.Name}\" for {selfType.FullName} ({sig.Signature})", false); } - var fallibility = sig!.Fallibility; - if (fallibility == Fallibility.Auto) - { - fallibility = info.IsNullable || wasWrapped - ? Fallibility.Fallible - : Fallibility.Infallible; - } + continue; + } - var fallible = fallibility == Fallibility.Fallible; - - void Invalid(string message, bool prepend = true) + switch (sig.UseFlags) + { + case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)): + case SignatureUseFlags.Pointer: { - var errorMsg = prepend - ? $"Invalid Signature attribute for {selfType.FullName}.{info.Name}: {message}" - : message; - if (fallible) + if (actualType.IsAssignableTo(typeof(Delegate))) { - PluginLog.Warning(errorMsg); + info.SetValue(self, Marshal.GetDelegateForFunctionPointer(ptr, actualType)); } else { - throw new SignatureException(errorMsg); + info.SetValue(self, ptr); } + + break; } - IntPtr ptr; - var success = sig.ScanType == ScanType.Text - ? scanner.TryScanText(sig.Signature, out ptr) - : scanner.TryGetStaticAddressFromSig(sig.Signature, out ptr); - if (!success) + case SignatureUseFlags.Auto when actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Hook<>): + case SignatureUseFlags.Hook: + { + if (!actualType.IsGenericType || actualType.GetGenericTypeDefinition() != typeof(Hook<>)) + { + Invalid($"{actualType.Name} is not a Hook"); + continue; + } + + var hookDelegateType = actualType.GenericTypeArguments[0]; + + Delegate? detour; + if (sig.DetourName == null) + { + var matches = selfType.GetMethods(Flags) + .Select(method => method.IsStatic + ? Delegate.CreateDelegate(hookDelegateType, method, false) + : Delegate.CreateDelegate(hookDelegateType, self, method, false)) + .Where(del => del != null) + .ToArray(); + if (matches.Length != 1) + { + Invalid("Either found no matching detours or found more than one: specify a detour name"); + continue; + } + + detour = matches[0]!; + } + else + { + var method = selfType.GetMethod(sig.DetourName, Flags); + if (method == null) + { + Invalid($"Could not find detour \"{sig.DetourName}\""); + continue; + } + + var del = method.IsStatic + ? Delegate.CreateDelegate(hookDelegateType, method, false) + : Delegate.CreateDelegate(hookDelegateType, self, method, false); + if (del == null) + { + Invalid($"Method {sig.DetourName} was not compatible with delegate {hookDelegateType.Name}"); + continue; + } + + detour = del; + } + + var ctor = actualType.GetConstructor(new[] { typeof(IntPtr), hookDelegateType }); + if (ctor == null) + { + PluginLog.Error("Error in SignatureHelper: could not find Hook constructor"); + continue; + } + + var hook = ctor.Invoke(new object?[] { ptr, detour }); + info.SetValue(self, hook); + + break; + } + + case SignatureUseFlags.Auto when actualType.IsPrimitive: + case SignatureUseFlags.Offset: + { + var offset = Marshal.PtrToStructure(ptr + sig.Offset, actualType); + info.SetValue(self, offset); + + break; + } + + default: { if (log) { - Invalid($"Failed to find {sig.ScanType} signature \"{info.Name}\" for {selfType.FullName} ({sig.Signature})", false); + Invalid("could not detect desired signature use, set SignatureUseFlags manually"); } - continue; - } - - switch (sig.UseFlags) - { - case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)): - case SignatureUseFlags.Pointer: - { - if (actualType.IsAssignableTo(typeof(Delegate))) - { - info.SetValue(self, Marshal.GetDelegateForFunctionPointer(ptr, actualType)); - } - else - { - info.SetValue(self, ptr); - } - - break; - } - - case SignatureUseFlags.Auto when actualType.IsGenericType && actualType.GetGenericTypeDefinition() == typeof(Hook<>): - case SignatureUseFlags.Hook: - { - if (!actualType.IsGenericType || actualType.GetGenericTypeDefinition() != typeof(Hook<>)) - { - Invalid($"{actualType.Name} is not a Hook"); - continue; - } - - var hookDelegateType = actualType.GenericTypeArguments[0]; - - Delegate? detour; - if (sig.DetourName == null) - { - var matches = selfType.GetMethods(Flags) - .Select(method => method.IsStatic - ? Delegate.CreateDelegate(hookDelegateType, method, false) - : Delegate.CreateDelegate(hookDelegateType, self, method, false)) - .Where(del => del != null) - .ToArray(); - if (matches.Length != 1) - { - Invalid("Either found no matching detours or found more than one: specify a detour name"); - continue; - } - - detour = matches[0]!; - } - else - { - var method = selfType.GetMethod(sig.DetourName, Flags); - if (method == null) - { - Invalid($"Could not find detour \"{sig.DetourName}\""); - continue; - } - - var del = method.IsStatic - ? Delegate.CreateDelegate(hookDelegateType, method, false) - : Delegate.CreateDelegate(hookDelegateType, self, method, false); - if (del == null) - { - Invalid($"Method {sig.DetourName} was not compatible with delegate {hookDelegateType.Name}"); - continue; - } - - detour = del; - } - - var ctor = actualType.GetConstructor(new[] { typeof(IntPtr), hookDelegateType }); - if (ctor == null) - { - PluginLog.Error("Error in SignatureHelper: could not find Hook constructor"); - continue; - } - - var hook = ctor.Invoke(new object?[] { ptr, detour }); - info.SetValue(self, hook); - - break; - } - - case SignatureUseFlags.Auto when actualType.IsPrimitive: - case SignatureUseFlags.Offset: - { - var offset = Marshal.PtrToStructure(ptr + sig.Offset, actualType); - info.SetValue(self, offset); - - break; - } - - default: - { - if (log) - { - Invalid("could not detect desired signature use, set SignatureUseFlags manually"); - } - - break; - } + break; } } } diff --git a/Dalamud/Utility/Signatures/SignatureUseFlags.cs b/Dalamud/Utility/Signatures/SignatureUseFlags.cs index 759352906..f952bc00e 100755 --- a/Dalamud/Utility/Signatures/SignatureUseFlags.cs +++ b/Dalamud/Utility/Signatures/SignatureUseFlags.cs @@ -2,39 +2,38 @@ using Dalamud.Hooking; -namespace Dalamud.Utility.Signatures +namespace Dalamud.Utility.Signatures; + +/// +/// Use flags for a signature attribute. This tells SignatureHelper how to use the +/// result of the signature. +/// +public enum SignatureUseFlags { /// - /// Use flags for a signature attribute. This tells SignatureHelper how to use the - /// result of the signature. + /// SignatureHelper will use simple heuristics to determine the best signature + /// use for the field/property. /// - public enum SignatureUseFlags - { - /// - /// SignatureHelper will use simple heuristics to determine the best signature - /// use for the field/property. - /// - Auto, + Auto, - /// - /// The signature should be used as a plain pointer. This is correct for - /// static addresses, functions, or anything else that's an - /// at heart. - /// - Pointer, + /// + /// The signature should be used as a plain pointer. This is correct for + /// static addresses, functions, or anything else that's an + /// at heart. + /// + Pointer, - /// - /// The signature should be used as a hook. This is correct for - /// fields/properties. - /// - Hook, + /// + /// The signature should be used as a hook. This is correct for + /// fields/properties. + /// + Hook, - /// - /// The signature should be used to determine an offset. This is the default - /// for all primitive types. SignatureHelper will read from the memory at this - /// signature and store the result in the field/property. An offset from the - /// signature can be specified in the . - /// - Offset, - } + /// + /// The signature should be used to determine an offset. This is the default + /// for all primitive types. SignatureHelper will read from the memory at this + /// signature and store the result in the field/property. An offset from the + /// signature can be specified in the . + /// + Offset, } diff --git a/Dalamud/Utility/Signatures/Wrappers/FieldInfoWrapper.cs b/Dalamud/Utility/Signatures/Wrappers/FieldInfoWrapper.cs index daad25bd7..79233eae2 100755 --- a/Dalamud/Utility/Signatures/Wrappers/FieldInfoWrapper.cs +++ b/Dalamud/Utility/Signatures/Wrappers/FieldInfoWrapper.cs @@ -1,43 +1,42 @@ using System; using System.Reflection; -namespace Dalamud.Utility.Signatures.Wrappers +namespace Dalamud.Utility.Signatures.Wrappers; + +/// +/// Class providing information about a field. +/// +internal sealed class FieldInfoWrapper : IFieldOrPropertyInfo { /// - /// Class providing information about a field. + /// Initializes a new instance of the class. /// - internal sealed class FieldInfoWrapper : IFieldOrPropertyInfo + /// FieldInfo to populate from. + public FieldInfoWrapper(FieldInfo info) { - /// - /// Initializes a new instance of the class. - /// - /// FieldInfo to populate from. - public FieldInfoWrapper(FieldInfo info) - { - this.Info = info; - } + this.Info = info; + } - /// - public string Name => this.Info.Name; + /// + public string Name => this.Info.Name; - /// - public Type ActualType => this.Info.FieldType; + /// + public Type ActualType => this.Info.FieldType; - /// - public bool IsNullable => NullabilityUtil.IsNullable(this.Info); + /// + public bool IsNullable => NullabilityUtil.IsNullable(this.Info); - private FieldInfo Info { get; } + private FieldInfo Info { get; } - /// - public void SetValue(object? self, object? value) - { - this.Info.SetValue(self, value); - } + /// + public void SetValue(object? self, object? value) + { + this.Info.SetValue(self, value); + } - /// - public T? GetCustomAttribute() where T : Attribute - { - return this.Info.GetCustomAttribute(); - } + /// + public T? GetCustomAttribute() where T : Attribute + { + return this.Info.GetCustomAttribute(); } } diff --git a/Dalamud/Utility/Signatures/Wrappers/IFieldOrPropertyInfo.cs b/Dalamud/Utility/Signatures/Wrappers/IFieldOrPropertyInfo.cs index 0c54c80ab..7690f86ec 100755 --- a/Dalamud/Utility/Signatures/Wrappers/IFieldOrPropertyInfo.cs +++ b/Dalamud/Utility/Signatures/Wrappers/IFieldOrPropertyInfo.cs @@ -1,39 +1,38 @@ using System; -namespace Dalamud.Utility.Signatures.Wrappers +namespace Dalamud.Utility.Signatures.Wrappers; + +/// +/// Interface providing information about a field or a property. +/// +internal interface IFieldOrPropertyInfo { /// - /// Interface providing information about a field or a property. + /// Gets the name of the field or property. /// - internal interface IFieldOrPropertyInfo - { - /// - /// Gets the name of the field or property. - /// - string Name { get; } + string Name { get; } - /// - /// Gets the actual type of the field or property. - /// - Type ActualType { get; } + /// + /// Gets the actual type of the field or property. + /// + Type ActualType { get; } - /// - /// Gets a value indicating whether or not the field or property is nullable. - /// - bool IsNullable { get; } + /// + /// Gets a value indicating whether or not the field or property is nullable. + /// + bool IsNullable { get; } - /// - /// Set this field or property's value. - /// - /// The object instance. - /// The value to set. - void SetValue(object? self, object? value); + /// + /// Set this field or property's value. + /// + /// The object instance. + /// The value to set. + void SetValue(object? self, object? value); - /// - /// Get a custom attribute. - /// - /// The type of the attribute. - /// The attribute. - T? GetCustomAttribute() where T : Attribute; - } + /// + /// Get a custom attribute. + /// + /// The type of the attribute. + /// The attribute. + T? GetCustomAttribute() where T : Attribute; } diff --git a/Dalamud/Utility/Signatures/Wrappers/PropertyInfoWrapper.cs b/Dalamud/Utility/Signatures/Wrappers/PropertyInfoWrapper.cs index 8ee3a102d..280e0c012 100755 --- a/Dalamud/Utility/Signatures/Wrappers/PropertyInfoWrapper.cs +++ b/Dalamud/Utility/Signatures/Wrappers/PropertyInfoWrapper.cs @@ -1,43 +1,42 @@ using System; using System.Reflection; -namespace Dalamud.Utility.Signatures.Wrappers +namespace Dalamud.Utility.Signatures.Wrappers; + +/// +/// Class providing information about a property. +/// +internal sealed class PropertyInfoWrapper : IFieldOrPropertyInfo { /// - /// Class providing information about a property. + /// Initializes a new instance of the class. /// - internal sealed class PropertyInfoWrapper : IFieldOrPropertyInfo + /// PropertyInfo. + public PropertyInfoWrapper(PropertyInfo info) { - /// - /// Initializes a new instance of the class. - /// - /// PropertyInfo. - public PropertyInfoWrapper(PropertyInfo info) - { - this.Info = info; - } + this.Info = info; + } - /// - public string Name => this.Info.Name; + /// + public string Name => this.Info.Name; - /// - public Type ActualType => this.Info.PropertyType; + /// + public Type ActualType => this.Info.PropertyType; - /// - public bool IsNullable => NullabilityUtil.IsNullable(this.Info); + /// + public bool IsNullable => NullabilityUtil.IsNullable(this.Info); - private PropertyInfo Info { get; } + private PropertyInfo Info { get; } - /// - public void SetValue(object? self, object? value) - { - this.Info.SetValue(self, value); - } + /// + public void SetValue(object? self, object? value) + { + this.Info.SetValue(self, value); + } - /// - public T? GetCustomAttribute() where T : Attribute - { - return this.Info.GetCustomAttribute(); - } + /// + public T? GetCustomAttribute() where T : Attribute + { + return this.Info.GetCustomAttribute(); } } diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index 1cd72c674..150f06c32 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -1,32 +1,31 @@ using System.Diagnostics.CodeAnalysis; -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([NotNullWhen(false)] 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([NotNullWhen(false)] 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([NotNullWhen(false)] 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([NotNullWhen(false)] 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 3878cdb25..eba6b562f 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -20,600 +20,599 @@ 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; + private static string? gitHashClientStructsInternal; + + private static ulong moduleStartAddr; + private static ulong moduleEndAddr; + /// - /// 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(); + + /// + /// Check two byte arrays for equality. + /// + /// The first byte array. + /// The second byte array. + /// Whether or not the byte arrays are equal. + public static unsafe bool FastByteArrayCompare(byte[]? a1, byte[]? a2) { - private static string? gitHashInternal; - private static string? gitHashClientStructsInternal; + // 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 - private static ulong moduleStartAddr; - private static ulong moduleEndAddr; - - /// - /// 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(); - - /// - /// Check two byte arrays for equality. - /// - /// The first byte array. - /// The second byte array. - /// Whether or not the byte arrays are equal. - public static unsafe bool FastByteArrayCompare(byte[]? a1, byte[]? a2) + if (a1 == a2) return true; + if (a1 == null || a2 == null || a1.Length != a2.Length) + return false; + fixed (byte* p1 = a1, p2 = 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) { - byte* x1 = p1, x2 = p2; - var l = a1.Length; - for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) - { - 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; + 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; } + } - /// - /// 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.First(a => a.Key == "GitHash").Value; - + /// + /// 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; - } - /// - /// Gets the git hash value from the assembly - /// or null if it cannot be found. - /// - /// The git hash of the assembly. - public static string GetGitHashClientStructs() - { - if (gitHashClientStructsInternal != null) - return gitHashClientStructsInternal; + var asm = typeof(Util).Assembly; + var attrs = asm.GetCustomAttributes(); - var asm = typeof(Util).Assembly; - var attrs = asm.GetCustomAttributes(); + gitHashInternal = attrs.First(a => a.Key == "GitHash").Value; - gitHashClientStructsInternal = attrs.First(a => a.Key == "GitHashClientStructs").Value; + return gitHashInternal; + } + /// + /// Gets the git hash value from the assembly + /// or null if it cannot be found. + /// + /// The git hash of the assembly. + public static string GetGitHashClientStructs() + { + if (gitHashClientStructsInternal != null) return gitHashClientStructsInternal; + + var asm = typeof(Util).Assembly; + var attrs = asm.GetCustomAttributes(); + + gitHashClientStructsInternal = attrs.First(a => a.Key == "GitHashClientStructs").Value; + + return gitHashClientStructsInternal; + } + + /// + /// 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 + { + SafeMemory.ReadBytes(offset, len, out var data); + Log.Information(ByteArrayToHex(data)); + } + catch (Exception ex) + { + 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); } - /// - /// 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) + return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); + } + + /// + /// Show a structure in an ImGui context. + /// + /// The structure to show. + /// The address to the structure. + /// Whether or not this structure should start out expanded. + /// The already followed path. + public static void ShowStruct(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null) + { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); + path ??= new List(); + + if (moduleEndAddr == 0 && moduleStartAddr == 0) { try { - SafeMemory.ReadBytes(offset, len, out var data); - Log.Information(ByteArrayToHex(data)); - } - catch (Exception ex) - { - 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++) + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) { - 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++; + moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); + moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; } - - sb.Append(line); - } - - return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); - } - - /// - /// Show a structure in an ImGui context. - /// - /// The structure to show. - /// The address to the structure. - /// Whether or not this structure should start out expanded. - /// The already followed path. - public static void ShowStruct(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null) - { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); - path ??= new List(); - - if (moduleEndAddr == 0 && moduleStartAddr == 0) - { - try - { - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) - { - moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); - moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; - } - else - { - moduleEndAddr = 1; - } - } - catch + else { moduleEndAddr = 1; } } - - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) + catch { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + moduleEndAddr = 1; } + } - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) + 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)) { - 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) { - 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.Text($"fixed"); ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); - ImGui.SameLine(); - - ShowValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); } - - foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) - { - 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(); - - ShowValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); - } - - ImGui.TreePop(); - } - else - { - ImGui.PopStyleColor(); - } - - ImGui.PopStyleVar(); - } - - /// - /// Show a structure in an ImGui context. - /// - /// The type of the structure. - /// The pointer to the structure. - /// Whether or not this structure should start out expanded. - public static unsafe void ShowStruct(T* obj, bool autoExpand = false) where T : unmanaged - { - ShowStruct(*obj, (ulong)&obj, autoExpand); - } - - /// - /// Show a GameObject's internal data in an ImGui-context. - /// - /// The GameObject to show. - /// Whether or not the struct should start as expanded. - public static unsafe void ShowGameObjectStruct(GameObject go, bool autoExpand = true) - { - switch (go) - { - case BattleChara bchara: - ShowStruct(bchara.Struct, autoExpand); - break; - case Character chara: - ShowStruct(chara.Struct, autoExpand); - break; - default: - ShowStruct(go.Struct, autoExpand); - break; - } - } - - /// - /// 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().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) - { - var value = propertyInfo.GetValue(obj); - var valueType = value?.GetType(); - if (valueType == typeof(IntPtr)) - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: 0x{value:X}"); else - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {value}"); + { + 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(); + + ShowValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); } - ImGui.Unindent(); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.HealerGreen, "-> Fields:"); - - ImGui.Indent(); - - foreach (var fieldInfo in type.GetFields()) + foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) { - ImGui.TextColored(ImGuiColors.HealerGreen, $" {fieldInfo.Name}: {fieldInfo.GetValue(obj)}"); + 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(); + + ShowValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); } - ImGui.Unindent(); + ImGui.TreePop(); + } + else + { + ImGui.PopStyleColor(); } - /// - /// Display an error MessageBox and exit the current process. - /// - /// MessageBox body. - /// MessageBox caption (title). - /// Specify whether to exit immediately. - public static void Fatal(string message, string caption, bool exit = true) - { - var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.Topmost; - _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + ImGui.PopStyleVar(); + } - if (exit) - Environment.Exit(-1); + /// + /// Show a structure in an ImGui context. + /// + /// The type of the structure. + /// The pointer to the structure. + /// Whether or not this structure should start out expanded. + public static unsafe void ShowStruct(T* obj, bool autoExpand = false) where T : unmanaged + { + ShowStruct(*obj, (ulong)&obj, autoExpand); + } + + /// + /// Show a GameObject's internal data in an ImGui-context. + /// + /// The GameObject to show. + /// Whether or not the struct should start as expanded. + public static unsafe void ShowGameObjectStruct(GameObject go, bool autoExpand = true) + { + switch (go) + { + case BattleChara bchara: + ShowStruct(bchara.Struct, autoExpand); + break; + case Character chara: + ShowStruct(chara.Struct, autoExpand); + break; + default: + ShowStruct(go.Struct, autoExpand); + break; } + } - /// - /// Transform byte count to human readable format. - /// - /// Number of bytes. - /// Human readable version. - public static string FormatBytes(long bytes) + /// + /// 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().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) { - 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.00} {suffix[i]}"; - } - - /// - /// 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})"); - } + var value = propertyInfo.GetValue(obj); + var valueType = value?.GetType(); + if (valueType == typeof(IntPtr)) + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: 0x{value:X}"); else - { - text = Encoding.UTF8.GetString(array, 0, count); - } - - return text; + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {value}"); } - /// - /// Compress a string using GZip. - /// - /// The input string. - /// The compressed output bytes. - public static byte[] CompressString(string str) + ImGui.Unindent(); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.HealerGreen, "-> Fields:"); + + ImGui.Indent(); + + foreach (var fieldInfo in type.GetFields()) { - 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)) - { - msi.CopyTo(gs); - } - - return mso.ToArray(); + ImGui.TextColored(ImGuiColors.HealerGreen, $" {fieldInfo.Name}: {fieldInfo.GetValue(obj)}"); } - /// - /// Decompress a string using GZip. - /// - /// The input bytes. - /// The compressed output string. - public static string DecompressString(byte[] bytes) + ImGui.Unindent(); + } + + /// + /// Display an error MessageBox and exit the current process. + /// + /// MessageBox body. + /// MessageBox caption (title). + /// Specify whether to exit immediately. + public static void Fatal(string message, string caption, bool exit = true) + { + var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.Topmost; + _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + + if (exit) + Environment.Exit(-1); + } + + /// + /// Transform byte count to human readable format. + /// + /// Number of bytes. + /// Human readable version. + public 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) { - using var msi = new MemoryStream(bytes); - using var mso = new MemoryStream(); - using (var gs = new GZipStream(msi, CompressionMode.Decompress)) - { - gs.CopyTo(mso); - } - - return Encoding.UTF8.GetString(mso.ToArray()); + dblSByte = bytes / 1024.0; } - /// - /// Copy one stream to another. - /// - /// The source stream. - /// The destination stream. - /// The maximum length to copy. - [Obsolete("Use Stream.CopyTo() instead", true)] - public static void CopyTo(Stream src, Stream dest, int len = 4069) + return $"{dblSByte:0.00} {suffix[i]}"; + } + + /// + /// 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++) { - var bytes = new byte[len]; - int cnt; - - while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) dest.Write(bytes, 0, cnt); + if (array[count] == 0) + break; } - /// - /// Heuristically determine if Dalamud is running on Linux/WINE. - /// - /// Whether or not Dalamud is running on Linux/WINE. - public static bool IsLinux() + string text; + if (count == array.Length) { - 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(); + text = Encoding.UTF8.GetString(array); + Log.Warning($"Warning: text exceeds underlying array length ({text})"); } - - /// - /// Open a link in the default browser. - /// - /// The link to open. - public static void OpenLink(string url) + else { - var process = new ProcessStartInfo(url) - { - UseShellExecute = true, - }; - Process.Start(process); + text = Encoding.UTF8.GetString(array, 0, count); } - /// - /// Dispose this object. - /// - /// The object to dispose. - /// The type of object to dispose. - internal static void ExplicitDispose(this T obj) where T : IDisposable + 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)) + { + msi.CopyTo(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)) + { + gs.CopyTo(mso); + } + + return Encoding.UTF8.GetString(mso.ToArray()); + } + + /// + /// Copy one stream to another. + /// + /// The source stream. + /// The destination stream. + /// The maximum length to copy. + [Obsolete("Use Stream.CopyTo() instead", true)] + 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(); + } + + /// + /// Open a link in the default browser. + /// + /// The link to open. + public static void OpenLink(string url) + { + var process = new ProcessStartInfo(url) + { + UseShellExecute = true, + }; + Process.Start(process); + } + + /// + /// Dispose this object. + /// + /// The object to dispose. + /// The type of object to dispose. + internal static void ExplicitDispose(this T obj) where T : IDisposable + { + obj.Dispose(); + } + + /// + /// Dispose this object. + /// + /// The object to dispose. + /// Log message to print, if specified and an error occurs. + /// Module logger, if any. + /// The type of object to dispose. + internal static void ExplicitDisposeIgnoreExceptions(this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable + { + try { obj.Dispose(); } - - /// - /// Dispose this object. - /// - /// The object to dispose. - /// Log message to print, if specified and an error occurs. - /// Module logger, if any. - /// The type of object to dispose. - internal static void ExplicitDisposeIgnoreExceptions(this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable + catch (Exception e) { - try - { - obj.Dispose(); - } - catch (Exception e) - { - if (logMessage == null) - return; + if (logMessage == null) + return; - if (moduleLog != null) - moduleLog.Error(e, logMessage); - else - Log.Error(e, logMessage); - } + if (moduleLog != null) + moduleLog.Error(e, logMessage); + else + Log.Error(e, logMessage); } + } - private static unsafe void ShowValue(ulong addr, IEnumerable path, Type type, object value) + private static unsafe void ShowValue(ulong addr, IEnumerable path, Type type, object value) + { + if (type.IsPointer) { - if (type.IsPointer) + var val = (Pointer)value; + var unboxed = Pointer.Unbox(val); + if (unboxed != null) { - var val = (Pointer)value; - var unboxed = Pointer.Unbox(val); - if (unboxed != null) + var unboxedAddr = (ulong)unboxed; + ImGuiHelpers.ClickToCopyText($"{(ulong)unboxed:X}"); + if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) { - var unboxedAddr = (ulong)unboxed; - ImGuiHelpers.ClickToCopyText($"{(ulong)unboxed:X}"); - if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) - { - ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); - ImGui.PopStyleColor(); - } + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); + ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); + ImGui.PopStyleColor(); + } - try + try + { + var eType = type.GetElementType(); + var ptrObj = SafeMemory.PtrToStructure(new IntPtr(unboxed), eType); + ImGui.SameLine(); + if (ptrObj == null) { - var eType = type.GetElementType(); - var ptrObj = SafeMemory.PtrToStructure(new IntPtr(unboxed), eType); - ImGui.SameLine(); - if (ptrObj == null) - { - ImGui.Text("null or invalid"); - } - else - { - ShowStruct(ptrObj, (ulong)unboxed, path: new List(path)); - } + ImGui.Text("null or invalid"); } - catch + else { - // Ignored + ShowStruct(ptrObj, (ulong)unboxed, path: new List(path)); } } - else + catch { - ImGui.Text("null"); + // Ignored } } else { - if (!type.IsPrimitive) - { - ShowStruct(value, addr, path: new List(path)); - } - else - { - ImGui.Text($"{value}"); - } + ImGui.Text("null"); + } + } + else + { + if (!type.IsPrimitive) + { + ShowStruct(value, addr, path: new List(path)); + } + else + { + ImGui.Text($"{value}"); } } } 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); }